From 87d155808afd537899b21c8dcda36020287a417c Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 30 Apr 2026 10:31:25 +0200 Subject: [PATCH 1/3] Don't check for linked shared libs and module files for each extension --- easybuild/framework/easyblock.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index 5f6661116e..d28319db3d 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -4439,20 +4439,21 @@ def xs2str(xs): else: self._sanity_check_step_extensions() - linked_shared_lib_fails = self.sanity_check_linked_shared_libs() - if linked_shared_lib_fails: - self.log.warning("Check for required/banned linked shared libraries failed!") - self.sanity_check_fail_msgs.append(linked_shared_lib_fails) - - # software installed with GCCcore toolchain should not have Fortran module files (.mod), - # unless that's explicitly allowed - if self.toolchain.name in ('GCCcore',) and not self.cfg['skip_mod_files_sanity_check']: - mod_files_found_msg = self.sanity_check_mod_files() - if mod_files_found_msg: - if build_option('fail_on_mod_files_gcccore'): - self.sanity_check_fail_msgs.append(mod_files_found_msg) - else: - print_warning(mod_files_found_msg) + # Do not do those checks for extensions, only in the main easyconfig + linked_shared_lib_fails = self.sanity_check_linked_shared_libs() + if linked_shared_lib_fails: + self.log.warning("Check for required/banned linked shared libraries failed!") + self.sanity_check_fail_msgs.append(linked_shared_lib_fails) + + # software installed with GCCcore toolchain should not have Fortran module files (.mod), + # unless that's explicitly allowed + if self.toolchain.name in ('GCCcore',) and not self.cfg['skip_mod_files_sanity_check']: + mod_files_found_msg = self.sanity_check_mod_files() + if mod_files_found_msg: + if build_option('fail_on_mod_files_gcccore'): + self.sanity_check_fail_msgs.append(mod_files_found_msg) + else: + print_warning(mod_files_found_msg) # cleanup if self.fake_mod_data: From ce3052100bf7b5ed3eb71c5a09dcf1d06c77067c Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 30 Apr 2026 15:33:56 +0200 Subject: [PATCH 2/3] Add extension to `test_toy_cuda_sanity_check` --- test/framework/toy_build.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 10beb5a45e..32327a981b 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -3087,6 +3087,8 @@ def test_toy_cuda_sanity_check(self): " 'cp %s %s/pytoy-cuda.cpython-39-x86_64-linux-gnu.%s'," % (toy_bin, py_site_pkgs, shlib_ext), " 'cp %s %s/plugins/libpytoy_cuda.%s'," % (toy_bin, py_site_pkgs, shlib_ext), "]", + "exts_list = [('bar', '0.0')]", + "exts_defaultclass = 'DummyExtension'", ]) write_file(toy_ec_cuda, toy_ec_txt) @@ -3235,7 +3237,7 @@ def assert_in_log_and_stdout(s): with self.mocked_stdout_stderr(): outtxt = self._test_toy_build(ec_file=toy_ec_cuda, extra_args=args, raise_error=True) stdout = self.get_stdout() - assert_cuda_report(missing_cc=0, additional_cc=0, missing_ptx=3, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=0, additional_cc=0, missing_ptx=4, log=outtxt, stdout=stdout) # Test case 1b: test with default options, --cuda-compute-capabilities=8.0 and a binary that contains # 7.0 and 9.0 device code and 8.0 PTX code. @@ -3256,13 +3258,13 @@ def assert_in_log_and_stdout(s): stdout = self.get_stdout() self.assertIn(device_additional_70_90_code_msg, outtxt) self.assertIn(device_missing_80_code_msg, outtxt) - assert_cuda_report(missing_cc=3, additional_cc=3, missing_ptx=0, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=4, additional_cc=4, missing_ptx=0, log=outtxt, stdout=stdout) # Test case 2: same as Test case 1, but add --cuda-sanity-check-error-on-failed-checks # This is expected to fail since there is missing device code for CC80 args = ['--cuda-compute-capabilities=8.0', '--cuda-sanity-check-error-on-failed-checks'] # We expect this to fail, so first check error, then run again to check output - error_pattern = r"Files missing CUDA device code: 3." + error_pattern = r"Files missing CUDA device code: 4." with self.mocked_stdout_stderr(): self.assertErrorRegex(EasyBuildError, error_pattern, self._test_toy_build, ec_file=toy_ec_cuda, extra_args=args, raise_error=True) @@ -3270,7 +3272,7 @@ def assert_in_log_and_stdout(s): stdout = self.get_stdout() self.assertIn(device_additional_70_90_code_msg, outtxt) self.assertIn(device_missing_80_code_msg, outtxt) - assert_cuda_report(missing_cc=3, additional_cc=3, missing_ptx=0, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=4, additional_cc=4, missing_ptx=0, log=outtxt, stdout=stdout) # Test case 3: same as Test case 2, but add --cuda-sanity-check-accept-ptx-as-devcode # This is expected to succeed, since now the PTX code for CC80 will be accepted as @@ -3284,14 +3286,14 @@ def assert_in_log_and_stdout(s): stdout = self.get_stdout() self.assertIn(device_additional_70_90_code_msg, outtxt) self.assertIn(device_missing_80_code_msg, outtxt) - assert_cuda_report(missing_cc=0, additional_cc=3, missing_ptx=0, log=outtxt, stdout=stdout, - missing_cc_but_ptx=3) + assert_cuda_report(missing_cc=0, additional_cc=4, missing_ptx=0, log=outtxt, stdout=stdout, + missing_cc_but_ptx=4) # Test case 4: same as Test case 2, but run with --cuda-compute-capabilities=9.0 # This is expected to fail: device code is present, but PTX code for the highest CC (9.0) is missing args = ['--cuda-compute-capabilities=9.0', '--cuda-sanity-check-error-on-failed-checks'] # We expect this to fail, so first check error, then run again to check output - error_pattern = r"Files missing CUDA PTX code: 3" + error_pattern = r"Files missing CUDA PTX code: 4" with self.mocked_stdout_stderr(): self.assertErrorRegex(EasyBuildError, error_pattern, self._test_toy_build, ec_file=toy_ec_cuda, extra_args=args, raise_error=True) @@ -3299,7 +3301,7 @@ def assert_in_log_and_stdout(s): stdout = self.get_stdout() self.assertIn(device_additional_70_code_msg, outtxt) - assert_cuda_report(missing_cc=0, additional_cc=3, missing_ptx=3, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=0, additional_cc=4, missing_ptx=4, log=outtxt, stdout=stdout) # Test case 5: same as Test case 4, but add --cuda-sanity-check-accept-missing-ptx # This is expected to succeed: device code is present, PTX code is missing, but that's accepted @@ -3314,7 +3316,7 @@ def assert_in_log_and_stdout(s): stdout = self.get_stdout() self.assertIn(device_additional_70_code_msg, outtxt) self.assertRegex(outtxt, warning_pattern) - assert_cuda_report(missing_cc=0, additional_cc=3, missing_ptx=3, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=0, additional_cc=4, missing_ptx=4, log=outtxt, stdout=stdout) # Test case 6: same as Test case 5, but add --cuda-sanity-check-strict # This is expected to fail: device code is present, PTX code is missing (but accepted due to option) @@ -3322,14 +3324,14 @@ def assert_in_log_and_stdout(s): args = ['--cuda-compute-capabilities=9.0', '--cuda-sanity-check-error-on-failed-checks', '--cuda-sanity-check-accept-missing-ptx', '--cuda-sanity-check-strict'] # We expect this to fail, so first check error, then run again to check output - error_pattern = r"Files with additional CUDA device code: 3" + error_pattern = r"Files with additional CUDA device code: 4" with self.mocked_stdout_stderr(): self.assertErrorRegex(EasyBuildError, error_pattern, self._test_toy_build, ec_file=toy_ec_cuda, extra_args=args, raise_error=True) outtxt = self._test_toy_build(ec_file=toy_ec_cuda, extra_args=args, raise_error=False, verify=False) stdout = self.get_stdout() self.assertIn(device_additional_70_code_msg, outtxt) - assert_cuda_report(missing_cc=0, additional_cc=3, missing_ptx=3, log=outtxt, stdout=stdout) + assert_cuda_report(missing_cc=0, additional_cc=4, missing_ptx=4, log=outtxt, stdout=stdout) # Test case 7: same as Test case 6, but add the failing file to the cuda_sanity_ignore_files # This is expected to succeed: the individual file which _would_ cause the sanity check to fail is From 80150fe6ab6ded25cd936dbc67838ac5c3434408 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 30 Apr 2026 16:19:40 +0200 Subject: [PATCH 3/3] Do checks for CUDA compute archs and rpath only in parent easyblock not extensions --- easybuild/framework/easyblock.py | 42 ++++++++++++++++---------------- test/framework/toy_build.py | 2 ++ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py index d28319db3d..12f0dd2b52 100644 --- a/easybuild/framework/easyblock.py +++ b/easybuild/framework/easyblock.py @@ -4455,33 +4455,33 @@ def xs2str(xs): else: print_warning(mod_files_found_msg) + if self.toolchain.use_rpath: + rpath_fails = self.sanity_check_rpath() + if rpath_fails: + self.log.warning("RPATH sanity check failed!") + self.sanity_check_fail_msgs.extend(rpath_fails) + else: + self.log.debug("Skipping RPATH sanity check") + + if 'CUDA' in [dep['name'] for dep in self.cfg.dependencies()]: + if shutil.which('cuobjdump'): + cuda_fails = self.sanity_check_cuda() + if cuda_fails: + self.log.warning("CUDA device code sanity check failed!") + self.sanity_check_fail_msgs.extend(cuda_fails) + else: + msg = "Failed to execute CUDA sanity check: cuobjdump not found\n" + msg += "CUDA module must be loaded for sanity check (or cuobjdump available in PATH)" + raise EasyBuildError(msg) + else: + self.log.debug("Skipping CUDA sanity check: CUDA is not in dependencies") + # cleanup if self.fake_mod_data: self.clean_up_fake_module(self.fake_mod_data) self.sanity_check_module_loaded = False self.fake_mod_data = None - if self.toolchain.use_rpath: - rpath_fails = self.sanity_check_rpath() - if rpath_fails: - self.log.warning("RPATH sanity check failed!") - self.sanity_check_fail_msgs.extend(rpath_fails) - else: - self.log.debug("Skipping RPATH sanity check") - - if 'CUDA' in [dep['name'] for dep in self.cfg.dependencies()]: - if shutil.which('cuobjdump'): - cuda_fails = self.sanity_check_cuda() - if cuda_fails: - self.log.warning("CUDA device code sanity check failed!") - self.sanity_check_fail_msgs.extend(cuda_fails) - else: - msg = "Failed to execute CUDA sanity check: cuobjdump not found\n" - msg += "CUDA module must be loaded for sanity check (or cuobjdump available in PATH)" - raise EasyBuildError(msg) - else: - self.log.debug("Skipping CUDA sanity check: CUDA is not in dependencies") - # pass or fail if self.sanity_check_fail_msgs: raise EasyBuildError( diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py index 32327a981b..3cabd78243 100644 --- a/test/framework/toy_build.py +++ b/test/framework/toy_build.py @@ -3238,6 +3238,8 @@ def assert_in_log_and_stdout(s): outtxt = self._test_toy_build(ec_file=toy_ec_cuda, extra_args=args, raise_error=True) stdout = self.get_stdout() assert_cuda_report(missing_cc=0, additional_cc=0, missing_ptx=4, log=outtxt, stdout=stdout) + self.assertEqual(outtxt.count("CUDA sanity check summary report"), 1, + "CUDA sanity check should be done exactly once") # Test case 1b: test with default options, --cuda-compute-capabilities=8.0 and a binary that contains # 7.0 and 9.0 device code and 8.0 PTX code.