Repository: easybuilders/easybuild-framework

Issue:
`sanity_check_rpath` should not check for `libcuda.so.1`
When installing Clang, e.g. `Clang-12.0.1-GCCcore-10.3.0.eb` with `--rpath`, it errors out with

```
== 2022-10-12 13:53:54,035 build_log.py:169 ERROR EasyBuild crashed with an error (at easybuild/RHEL8/2022/software/EasyBuild/4.6.1/lib/python3.6/site-packages/easybuild/base/exceptions.py:124 in __init__): Sanity check failed: One or more required libraries not found for /home/casparl/.local/easybuild/RHEL8/2022/software/Clang/12.0.1-GCCcore-10.3.0/lib/libomptarget.rtl.cuda.so:   linux-vdso.so.1 (0x00007fffb959f000)
        libcuda.so.1 => not found
        libelf.so.1 => /sw/arch/Centos8/EB_production/2021/software/elfutils/0.185-GCCcore-10.3.0/lib64/libelf.so.1 (0x0000152603766000)
        libstdc++.so.6 => /home/casparl/.local/easybuild/Centos8/2021/software/GCCcore/10.3.0/lib64/libstdc++.so.6 (0x000015260339b000)
        libm.so.6 => /lib64/libm.so.6 (0x0000152603019000)
        libgcc_s.so.1 => /home/casparl/.local/easybuild/Centos8/2021/software/GCCcore/10.3.0/lib64/libgcc_s.so.1 (0x000015260374a000)
        libc.so.6 => /lib64/libc.so.6 (0x0000152602c54000)
        /lib64/ld-linux-x86-64.so.2 (0x0000152603582000)
        libz.so.1 => /sw/arch/Centos8/EB_production/2021/software/zlib/1.2.11-GCCcore-10.3.0/lib/../lib64/libz.so.1 (0x0000152603726000)

One or more required libraries not found for /home/casparl/.local/easybuild/RHEL8/2022/software/Clang/12.0.1-GCCcore-10.3.0/lib64/libomptarget.rtl.cuda.so:     linux-vdso.so.1 (0x000014f10ef12000)
        libcuda.so.1 => not found
        libelf.so.1 => /sw/arch/Centos8/EB_production/2021/software/elfutils/0.185-GCCcore-10.3.0/lib64/libelf.so.1 (0x000014f10eec6000)
        libstdc++.so.6 => /home/casparl/.local/easybuild/Centos8/2021/software/GCCcore/10.3.0/lib64/libstdc++.so.6 (0x000014f10eb01000)
        libm.so.6 => /lib64/libm.so.6 (0x000014f10e77f000)
        libgcc_s.so.1 => /home/casparl/.local/easybuild/Centos8/2021/software/GCCcore/10.3.0/lib64/libgcc_s.so.1 (0x000014f10eeaa000)
        libc.so.6 => /lib64/libc.so.6 (0x000014f10e3ba000)
        /lib64/ld-linux-x86-64.so.2 (0x000014f10ece8000)
        libz.so.1 => /sw/arch/Centos8/EB_production/2021/software/zlib/1.2.11-GCCcore-10.3.0/lib/../lib64/libz.so.1 (0x000014f10ee86000)
 (at easybuild/RHEL8/2022/software/EasyBuild/4.6.1/lib/python3.6/site-packages/easybuild/framework/easyblock.py:3471 in _sanity_check_step)
```

However, `libcuda.so.1` should _never_ be `RPATH`-ed. Nvidia supports cross compilation by linking (at compile time) against `stubs`  libraries. Those `stubs` libraries contain function declarations, but no implementations. Thus, at runtime, they are _never_ meant to be used. See https://github.com/easybuilders/easybuild-framework/issues/2683 

We made sure that the `RPATH` wrappers allow specifying certain paths to _never_ be `RPATH`-ed, and we use that to exclude the `stubs`  libraries from `CUDA`. See for the implementation https://github.com/easybuilders/easybuild-framework/pull/2725 and  https://github.com/easybuilders/easybuild-framework/pull/3850

The problem that we _didn't_ tackle is that if you filter those directories from being `RPATH`-ed, the `sanity_check_rpath`, the libraries that are linked from those directories will fail the `sanity_check_rpath` (as above).

*Minimal reproducer*
Put the following cpp in a `tar.gz`:
```
# toy.cpp
// Your First C++ Program

#include <iostream>

int main() {
    std::cout << "Hello World!";
    return 0;
}
```
Then create an EasyConfig that does e.g.
```
# toy-1.0.0-GCCcore-11.3.0.eb

easyblock='CmdCp'

name = 'Toy'
version = '1.0.0'

homepage = 'https://whatever.org/'
description = """Toy"""

toolchain = {'name': 'GCCcore', 'version': '11.3.0'}

sources = [
    'toy.tar.gz',
]

cmds_map = [('toy', 'g++ -o toy -lcuda toy.cpp')]

files_to_copy = [(["toy"], 'bin')]

builddependencies = [
    ('binutils', '2.38'),
]

dependencies = [
    ('CUDA', '11.7.0', '', SYSTEM),
]

sanity_check_paths = {
    'files': ['bin/toy'],
    'dirs': [],
}

moduleclass = 'tools'
```
Then, try to install with
```
eb toy-1.0.0-GCCcore-11.3.0-eb --sourcepath=<path/containing/toy/tarball>
```
We discussed a bit on the Slack, and we should probably alter https://github.com/easybuilders/easybuild-framework/blob/0a506aa01407785429038f243941b2526bba646f/easybuild/framework/easyblock.py#L2966 to skip certain libraries, much like we hard-enabled filtering `RPATH` for de CUDA stubs dir in https://github.com/easybuilders/easybuild-framework/blob/6cab5e2aa6d03ffcd98288bfa648e0a264954cee/easybuild/tools/toolchain/toolchain.py#L961


Discussion:

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -37,6 +37,7 @@
 :author: Damian Alvarez (Forschungszentrum Juelich GmbH)
 :author: Maxime Boissonneault (Compute Canada)
 :author: Davide Vanzo (Vanderbilt University)
+:author: Caspar van Leeuwen (SURF)
 """
 
 import copy
@@ -3048,9 +3049,16 @@ def sanity_check_rpath(self, rpath_dirs=None):
         self.log.debug("$LD_LIBRARY_PATH during RPATH sanity check: %s", os.getenv('LD_LIBRARY_PATH', '(empty)'))
         self.log.debug("List of loaded modules: %s", self.modules_tool.list())
 
-        not_found_regex = re.compile('not found', re.M)
+        not_found_regex = re.compile(r'(\S+)\s*\=\>\s*not found')
         readelf_rpath_regex = re.compile('(RPATH)', re.M)
 
+        # List of libraries that should be exempt from the RPATH sanity check;
+        # For example, libcuda.so.1 should never be RPATH-ed by design,
+        # see https://github.com/easybuilders/easybuild-framework/issues/4095
+        filter_rpath_sanity_libs = build_option('filter_rpath_sanity_libs')
+        msg = "Ignoring the following libraries if they are not found by RPATH sanity check: %s"
+        self.log.info(msg, filter_rpath_sanity_libs)
+
         if rpath_dirs is None:
             rpath_dirs = self.cfg['bin_lib_subdirs'] or self.bin_lib_subdirs()
 
@@ -3077,10 +3085,18 @@ def sanity_check_rpath(self, rpath_dirs=None):
                         self.log.debug(msg, path)
                     else:
                         # check whether all required libraries are found via 'ldd'
-                        if not_found_regex.search(out):
-                            fail_msg = "One or more required libraries not found for %s: %s" % (path, out)
-                            self.log.warning(fail_msg)
-                            fails.append(fail_msg)
+                        matches = re.findall(not_found_regex, out)
+                        if len(matches) > 0:  # Some libraries are not found via 'ldd'
+                            # For each match, check if the library is in the exception list
+                            for match in matches:
+                                if match in filter_rpath_sanity_libs:
+                                    msg = "Library %s not found for %s, but ignored "
+                                    msg += "since it is on the rpath exception list: %s"
+                                    self.log.info(msg, match, path, filter_rpath_sanity_libs)
+                                else:
+                                    fail_msg = "Library %s not found for %s" % (match, path)
+                                    self.log.warning(fail_msg)
+                                    fails.append(fail_msg)
                         else:
                             self.log.debug("Output of 'ldd %s' checked, looks OK", path)
 
diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py
--- a/easybuild/tools/config.py
+++ b/easybuild/tools/config.py
@@ -116,6 +116,16 @@
 DEFAULT_PR_TARGET_ACCOUNT = 'easybuilders'
 DEFAULT_PREFIX = os.path.join(os.path.expanduser('~'), ".local", "easybuild")
 DEFAULT_REPOSITORY = 'FileRepository'
+# Filter these CUDA libraries by default from the RPATH sanity check.
+# These are the only four libraries for which the CUDA toolkit ships stubs. By design, one is supposed to build
+# against the stub versions, but use the libraries that come with the CUDA driver at runtime. That means they should
+# never be RPATH-ed, and thus the sanity check should also accept that they aren't RPATH-ed.
+DEFAULT_FILTER_RPATH_SANITY_LIBS = (
+    'libcuda.so',
+    'libcuda.so.1',
+    'libnvidia-ml.so',
+    'libnvidia-ml.so.1'
+)
 DEFAULT_WAIT_ON_LOCK_INTERVAL = 60
 DEFAULT_WAIT_ON_LOCK_LIMIT = 0
 
@@ -205,6 +215,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
         'filter_deps',
         'filter_ecs',
         'filter_env_vars',
+        'filter_rpath_sanity_libs',
         'force_download',
         'git_working_dirs_path',
         'github_user',
diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py
--- a/easybuild/tools/options.py
+++ b/easybuild/tools/options.py
@@ -69,6 +69,7 @@
 from easybuild.tools.config import DEFAULT_MODULECLASSES, DEFAULT_PATH_SUBDIRS, DEFAULT_PKG_RELEASE, DEFAULT_PKG_TOOL
 from easybuild.tools.config import DEFAULT_PKG_TYPE, DEFAULT_PNS, DEFAULT_PREFIX, DEFAULT_PR_TARGET_ACCOUNT
 from easybuild.tools.config import DEFAULT_REPOSITORY, DEFAULT_WAIT_ON_LOCK_INTERVAL, DEFAULT_WAIT_ON_LOCK_LIMIT
+from easybuild.tools.config import DEFAULT_FILTER_RPATH_SANITY_LIBS
 from easybuild.tools.config import EBROOT_ENV_VAR_ACTIONS, ERROR, FORCE_DOWNLOAD_CHOICES, GENERAL_CLASS, IGNORE
 from easybuild.tools.config import JOB_DEPS_TYPE_ABORT_ON_ERROR, JOB_DEPS_TYPE_ALWAYS_RUN, LOADED_MODULES_ACTIONS
 from easybuild.tools.config import LOCAL_VAR_NAMING_CHECK_WARN, LOCAL_VAR_NAMING_CHECKS
@@ -410,6 +411,11 @@ def override_options(self):
                            'strlist', 'extend', None),
             'filter-env-vars': ("List of names of environment variables that should *not* be defined/updated by "
                                 "module files generated by EasyBuild", 'strlist', 'extend', None),
+            'filter-rpath-sanity-libs': ("List of libraries that should be ignored by the RPATH sanity check. "
+                                         "I.e. if these libraries are not RPATH-ed, that will be accepted "
+                                         "by the RPATH sanity check. Note that you'll need to provide the exact "
+                                         "library name, as it is returned by 'ldd', including any version ",
+                                         'strlist', 'store', DEFAULT_FILTER_RPATH_SANITY_LIBS),
             'fixed-installdir-naming-scheme': ("Use fixed naming scheme for installation directories", None,
                                                'store_true', True),
             'force-download': ("Force re-downloading of sources and/or patches, "


Test patch:
diff --git a/test/framework/easyconfigs/test_ecs/t/toy-app/toy-app-0.0.eb b/test/framework/easyconfigs/test_ecs/t/toy-app/toy-app-0.0.eb
new file mode 100644
--- /dev/null
+++ b/test/framework/easyconfigs/test_ecs/t/toy-app/toy-app-0.0.eb
@@ -0,0 +1,30 @@
+easyblock = 'EB_toy'
+
+name = 'toy-app'
+version = '0.0'
+
+homepage = 'https://easybuilders.github.io/easybuild'
+description = "Toy C program, 100% toy. This toy has a main function that depends on libtoy."
+
+toolchain = SYSTEM
+
+sources = [SOURCE_TAR_GZ]
+checksums = [
+    '9559393c0d747a4940a79be54e82fa8f14dbb0c32979a3e61e9db305f32dad49',  # default (SHA256)
+]
+
+dependencies = [
+    ('libtoy', '0.0')
+]
+
+buildopts = '-ltoy'
+
+sanity_check_paths = {
+    'files': [('bin/toy-app')],
+    'dirs': ['bin'],
+}
+
+postinstallcmds = ["echo TOY > %(installdir)s/README"]
+
+moduleclass = 'tools'
+# trailing comment, leave this here, it may trigger bugs with extract_comments()
diff --git a/test/framework/filetools.py b/test/framework/filetools.py
--- a/test/framework/filetools.py
+++ b/test/framework/filetools.py
@@ -2387,7 +2387,7 @@ def test_index_functions(self):
         # test with specified path with and without trailing '/'s
         for path in [test_ecs, test_ecs + '/', test_ecs + '//']:
             index = ft.create_index(path)
-            self.assertEqual(len(index), 90)
+            self.assertEqual(len(index), 91)
 
             expected = [
                 os.path.join('b', 'bzip2', 'bzip2-1.0.6-GCC-4.9.2.eb'),
diff --git a/test/framework/sandbox/sources/toy-app/toy-app-0.0.tar.gz b/test/framework/sandbox/sources/toy-app/toy-app-0.0.tar.gz
new file mode 100644
Binary files /dev/null and b/test/framework/sandbox/sources/toy-app/toy-app-0.0.tar.gz differ
diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py
--- a/test/framework/toy_build.py
+++ b/test/framework/toy_build.py
@@ -106,7 +106,7 @@ def tearDown(self):
         if os.path.exists(self.dummylogfn):
             os.remove(self.dummylogfn)
 
-    def check_toy(self, installpath, outtxt, version='0.0', versionprefix='', versionsuffix='', error=None):
+    def check_toy(self, installpath, outtxt, name='toy', version='0.0', versionprefix='', versionsuffix='', error=None):
         """Check whether toy build succeeded."""
 
         full_version = ''.join([versionprefix, version, versionsuffix])
@@ -121,38 +121,39 @@ def check_toy(self, installpath, outtxt, version='0.0', versionprefix='', versio
         self.assertTrue(success.search(outtxt), "COMPLETED message found in '%s'%s" % (outtxt, error_msg))
 
         # if the module exists, it should be fine
-        toy_module = os.path.join(installpath, 'modules', 'all', 'toy', full_version)
+        toy_module = os.path.join(installpath, 'modules', 'all', name, full_version)
         msg = "module for toy build toy/%s found (path %s)" % (full_version, toy_module)
         if get_module_syntax() == 'Lua':
             toy_module += '.lua'
         self.assertTrue(os.path.exists(toy_module), msg + error_msg)
 
         # module file is symlinked according to moduleclass
-        toy_module_symlink = os.path.join(installpath, 'modules', 'tools', 'toy', full_version)
+        toy_module_symlink = os.path.join(installpath, 'modules', 'tools', name, full_version)
         if get_module_syntax() == 'Lua':
             toy_module_symlink += '.lua'
         self.assertTrue(os.path.islink(toy_module_symlink))
         self.assertTrue(os.path.exists(toy_module_symlink))
 
         # make sure installation log file and easyconfig file are copied to install dir
-        software_path = os.path.join(installpath, 'software', 'toy', full_version)
-        install_log_path_pattern = os.path.join(software_path, 'easybuild', 'easybuild-toy-%s*.log' % version)
+        software_path = os.path.join(installpath, 'software', name, full_version)
+        install_log_path_pattern = os.path.join(software_path, 'easybuild', 'easybuild-%s-%s*.log' % (name, version))
         self.assertTrue(len(glob.glob(install_log_path_pattern)) >= 1,
                         "Found  at least 1 file at %s" % install_log_path_pattern)
 
         # make sure test report is available
-        test_report_path_pattern = os.path.join(software_path, 'easybuild', 'easybuild-toy-%s*test_report.md' % version)
+        report_name = 'easybuild-%s-%s*test_report.md' % (name, version)
+        test_report_path_pattern = os.path.join(software_path, 'easybuild', report_name)
         self.assertTrue(len(glob.glob(test_report_path_pattern)) >= 1,
                         "Found  at least 1 file at %s" % test_report_path_pattern)
 
-        ec_file_path = os.path.join(software_path, 'easybuild', 'toy-%s.eb' % full_version)
+        ec_file_path = os.path.join(software_path, 'easybuild', '%s-%s.eb' % (name, full_version))
         self.assertTrue(os.path.exists(ec_file_path))
 
-        devel_module_path = os.path.join(software_path, 'easybuild', 'toy-%s-easybuild-devel' % full_version)
+        devel_module_path = os.path.join(software_path, 'easybuild', '%s-%s-easybuild-devel' % (name, full_version))
         self.assertTrue(os.path.exists(devel_module_path))
 
     def test_toy_build(self, extra_args=None, ec_file=None, tmpdir=None, verify=True, fails=False, verbose=True,
-                       raise_error=False, test_report=None, versionsuffix='', testing=True,
+                       raise_error=False, test_report=None, name='toy', versionsuffix='', testing=True,
                        raise_systemexit=False, force=True, test_report_regexs=None):
         """Perform a toy build."""
         if extra_args is None:
@@ -188,7 +189,7 @@ def test_toy_build(self, extra_args=None, ec_file=None, tmpdir=None, verify=True
                 raise myerr
 
         if verify:
-            self.check_toy(self.test_installpath, outtxt, versionsuffix=versionsuffix, error=myerr)
+            self.check_toy(self.test_installpath, outtxt, name=name, versionsuffix=versionsuffix, error=myerr)
 
         if test_readme:
             # make sure postinstallcmds were used
@@ -2660,7 +2661,7 @@ def grab_gcc_rpath_wrapper_args():
 
             return {'filter_paths': res_filter.group(1), 'include_paths': res_include.group(1)}
 
-        args = ['--rpath', '--experimental']
+        args = ['--rpath']
         self.test_toy_build(extra_args=args, raise_error=True)
 
         # by default, /lib and /usr are included in RPATH filter,
@@ -2672,7 +2673,7 @@ def grab_gcc_rpath_wrapper_args():
         self.assertTrue(any(p.startswith(self.test_buildpath) for p in rpath_filter_paths))
 
         # Check that we can use --rpath-override-dirs
-        args = ['--rpath', '--experimental', '--rpath-override-dirs=/opt/eessi/2021.03/lib:/opt/eessi/lib']
+        args = ['--rpath', '--rpath-override-dirs=/opt/eessi/2021.03/lib:/opt/eessi/lib']
         self.test_toy_build(extra_args=args, raise_error=True)
         rpath_include_paths = grab_gcc_rpath_wrapper_args()['include_paths'].split(',')
         # Make sure our directories appear in dirs to be included in the rpath (and in the right order)
@@ -2680,7 +2681,7 @@ def grab_gcc_rpath_wrapper_args():
         self.assertEqual(rpath_include_paths[1], '/opt/eessi/lib')
 
         # Check that when we use --rpath-override-dirs empty values are filtered
-        args = ['--rpath', '--experimental', '--rpath-override-dirs=/opt/eessi/2021.03/lib::/opt/eessi/lib']
+        args = ['--rpath', '--rpath-override-dirs=/opt/eessi/2021.03/lib::/opt/eessi/lib']
         self.test_toy_build(extra_args=args, raise_error=True)
         rpath_include_paths = grab_gcc_rpath_wrapper_args()['include_paths'].split(',')
         # Make sure our directories appear in dirs to be included in the rpath (and in the right order)
@@ -2688,7 +2689,7 @@ def grab_gcc_rpath_wrapper_args():
         self.assertEqual(rpath_include_paths[1], '/opt/eessi/lib')
 
         # Check that when we use --rpath-override-dirs we can only provide absolute paths
-        eb_args = ['--rpath', '--experimental', '--rpath-override-dirs=/opt/eessi/2021.03/lib:eessi/lib']
+        eb_args = ['--rpath', '--rpath-override-dirs=/opt/eessi/2021.03/lib:eessi/lib']
         error_pattern = r"Path used in rpath_override_dirs is not an absolute path: eessi/lib"
         self.assertErrorRegex(EasyBuildError, error_pattern, self.test_toy_build, extra_args=eb_args, raise_error=True,
                               verbose=False)
@@ -2710,7 +2711,67 @@ def grab_gcc_rpath_wrapper_args():
         toy_ec_txt += "\ntoolchainopts = {'rpath': False}\n"
         toy_ec = os.path.join(self.test_prefix, 'toy.eb')
         write_file(toy_ec, toy_ec_txt)
-        self.test_toy_build(ec_file=toy_ec, extra_args=['--rpath', '--experimental'], raise_error=True)
+        self.test_toy_build(ec_file=toy_ec, extra_args=['--rpath'], raise_error=True)
+
+    def test_toy_filter_rpath_sanity_libs(self):
+        """Test use of --filter-rpath-sanity-libs."""
+
+        test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
+        toy_ec = os.path.join(test_ecs, 't', 'toy-app', 'toy-app-0.0.eb')
+
+        # This should just build succesfully
+        args = ['--rpath']
+        self.test_toy_build(ec_file=toy_ec, name='toy-app', extra_args=args, raise_error=True)
+
+        libtoy_libdir = os.path.join(self.test_installpath, 'software', 'libtoy', '0.0', 'lib')
+        toyapp_bin = os.path.join(self.test_installpath, 'software', 'toy-app', '0.0', 'bin', 'toy-app')
+        rpath_regex = re.compile(r"RPATH.*%s" % libtoy_libdir, re.M)
+        out, ec = run_cmd("readelf -d %s" % toyapp_bin, simple=False)
+        self.assertTrue(rpath_regex.search(out), "Pattern '%s' should be found in: %s" % (rpath_regex.pattern, out))
+
+        out, ec = run_cmd("ldd %s" % toyapp_bin, simple=False)
+        libtoy_regex = re.compile(r"libtoy.so => /.*/libtoy.so", re.M)
+        notfound = re.compile(r"libtoy\.so\s*=>\s*not found", re.M)
+        self.assertTrue(libtoy_regex.search(out), "Pattern '%s' should be found in: %s" % (libtoy_regex.pattern, out))
+        self.assertFalse(notfound.search(out), "Pattern '%s' should not be found in: %s" % (notfound.pattern, out))
+
+        # test sanity error when --rpath-filter is used to filter a required library
+        # In this test, libtoy.so will be linked, but not RPATH-ed due to the --rpath-filter
+        # Thus, the RPATH sanity check is expected to fail with libtoy.so not being found
+        error_pattern = r"Sanity check failed\: Library libtoy\.so not found"
+        self.assertErrorRegex(EasyBuildError, error_pattern, self.test_toy_build, ec_file=toy_ec,
+                              extra_args=['--rpath', '--rpath-filter=.*libtoy.*'],
+                              name='toy-app', raise_error=True, verbose=False)
+
+        # test use of --filter-rpath-sanity-libs option. In this test, we use --rpath-filter to make sure libtoy.so is
+        # not rpath-ed. Then, we use --filter-rpath-sanity-libs to make sure the RPATH sanity checks ignores
+        # the fact that libtoy.so is not found. Thus, this build should complete succesfully
+        args = ['--rpath', '--rpath-filter=.*libtoy.*', '--filter-rpath-sanity-libs=libtoy.so']
+        self.test_toy_build(ec_file=toy_ec, name='toy-app', extra_args=args, raise_error=True)
+
+        out, ec = run_cmd("readelf -d %s" % toyapp_bin, simple=False)
+        self.assertFalse(rpath_regex.search(out),
+                         "Pattern '%s' should not be found in: %s" % (rpath_regex.pattern, out))
+
+        out, ec = run_cmd("ldd %s" % toyapp_bin, simple=False)
+        self.assertFalse(libtoy_regex.search(out),
+                         "Pattern '%s' should not be found in: %s" % (libtoy_regex.pattern, out))
+        self.assertTrue(notfound.search(out),
+                        "Pattern '%s' should be found in: %s" % (notfound.pattern, out))
+
+        # test again with list of library names passed to --filter-rpath-sanity-libs
+        args = ['--rpath', '--rpath-filter=.*libtoy.*', '--filter-rpath-sanity-libs=libfoo.so,libtoy.so,libbar.so']
+        self.test_toy_build(ec_file=toy_ec, name='toy-app', extra_args=args, raise_error=True)
+
+        out, ec = run_cmd("readelf -d %s" % toyapp_bin, simple=False)
+        self.assertFalse(rpath_regex.search(out),
+                         "Pattern '%s' should not be found in: %s" % (rpath_regex.pattern, out))
+
+        out, ec = run_cmd("ldd %s" % toyapp_bin, simple=False)
+        self.assertFalse(libtoy_regex.search(out),
+                         "Pattern '%s' should not be found in: %s" % (libtoy_regex.pattern, out))
+        self.assertTrue(notfound.search(out),
+                        "Pattern '%s' should be found in: %s" % (notfound.pattern, out))
 
     def test_toy_modaltsoftname(self):
         """Build two dependent toys as in test_toy_toy but using modaltsoftname"""
@@ -2780,7 +2841,7 @@ def test_toy_build_trace(self):
 
         self.mock_stderr(True)
         self.mock_stdout(True)
-        self.test_toy_build(ec_file=test_ec, extra_args=['--trace', '--experimental'], verify=False, testing=False)
+        self.test_toy_build(ec_file=test_ec, extra_args=['--trace'], verify=False, testing=False)
         stderr = self.get_stderr()
         stdout = self.get_stdout()
         self.mock_stderr(False)