Repository: easybuilders/easybuild-framework

Issue:
symlink 'lib' to 'lib64' if 'lib' directory is missing
Similar to #3401, but the other way around...

Should fix some problems on OpenSUSE, see https://github.com/easybuilders/easybuild-easyblocks/issues/2309 and https://github.com/easybuilders/easybuild-easyconfigs/issues/11976


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
@@ -2410,18 +2410,27 @@ def post_install_step(self):
                 run_cmd(cmd, simple=True, log_ok=True, log_all=True)
 
         self.fix_shebang()
+
+        lib_dir = os.path.join(self.installdir, 'lib')
+        lib64_dir = os.path.join(self.installdir, 'lib64')
+
         # GCC linker searches system /lib64 path before the $LIBRARY_PATH paths.
         # However for each <dir> in $LIBRARY_PATH (where <dir> is often <prefix>/lib) it searches <dir>/../lib64 first.
         # So we create <prefix>/lib64 as a symlink to <prefix>/lib to make it prefer EB installed libraries.
         # See https://github.com/easybuilders/easybuild-easyconfigs/issues/5776
         if build_option('lib64_lib_symlink'):
-            lib_dir = os.path.join(self.installdir, 'lib')
-            lib64_dir = os.path.join(self.installdir, 'lib64')
             if os.path.exists(lib_dir) and not os.path.exists(lib64_dir):
                 # create *relative* 'lib64' symlink to 'lib';
                 # see https://github.com/easybuilders/easybuild-framework/issues/3564
                 symlink('lib', lib64_dir, use_abspath_source=False)
 
+        # symlink lib to lib64, which is helpful on OpenSUSE;
+        # see https://github.com/easybuilders/easybuild-framework/issues/3549
+        if build_option('lib_lib64_symlink'):
+            if os.path.exists(lib64_dir) and not os.path.exists(lib_dir):
+                # create *relative* 'lib' symlink to 'lib64';
+                symlink('lib64', lib_dir, use_abspath_source=False)
+
     def sanity_check_step(self, *args, **kwargs):
         """
         Do a sanity check on the installation
diff --git a/easybuild/tools/config.py b/easybuild/tools/config.py
--- a/easybuild/tools/config.py
+++ b/easybuild/tools/config.py
@@ -275,6 +275,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
         'cleanup_tmpdir',
         'extended_dry_run_ignore_errors',
         'fixed_installdir_naming_scheme',
+        'lib_lib64_symlink',
         'lib64_fallback_sanity_check',
         'lib64_lib_symlink',
         'mpi_tests',
diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py
--- a/easybuild/tools/options.py
+++ b/easybuild/tools/options.py
@@ -408,6 +408,8 @@ def override_options(self):
             'ignore-checksums': ("Ignore failing checksum verification", None, 'store_true', False),
             'ignore-osdeps': ("Ignore any listed OS dependencies", None, 'store_true', False),
             'install-latest-eb-release': ("Install latest known version of easybuild", None, 'store_true', False),
+            'lib-lib64-symlink': ("Automatically create symlinks for lib/ pointing to lib64/ if the former is missing",
+                                  None, 'store_true', True),
             'lib64-fallback-sanity-check': ("Fallback in sanity check to lib64/ equivalent for missing libraries",
                                             None, 'store_true', True),
             'lib64-lib-symlink': ("Automatically create symlinks for lib64/ pointing to lib/ if the former is missing",


Test patch:
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
@@ -3133,25 +3133,29 @@ def test_toy_build_unicode_description(self):
 
         self.test_toy_build(ec_file=test_ec, raise_error=True)
 
-    def test_test_toy_build_lib64_symlink(self):
+    def test_test_toy_build_lib64_lib_symlink(self):
         """Check whether lib64 symlink to lib subdirectory is created."""
         # this is done to ensure that <installdir>/lib64 is considered before /lib64 by GCC linker,
         # see https://github.com/easybuilders/easybuild-easyconfigs/issues/5776
 
-        # by default, lib64 symlink is created
+        # by default, lib64 -> lib symlink is created (--lib64-lib-symlink is enabled by default)
         self.test_toy_build()
 
         toy_installdir = os.path.join(self.test_installpath, 'software', 'toy', '0.0')
         lib_path = os.path.join(toy_installdir, 'lib')
         lib64_path = os.path.join(toy_installdir, 'lib64')
 
+        # lib64 subdir exists, is not a symlink
         self.assertTrue(os.path.exists(lib_path))
-        self.assertTrue(os.path.exists(lib64_path))
         self.assertTrue(os.path.isdir(lib_path))
         self.assertFalse(os.path.islink(lib_path))
+
+        # lib64 subdir is a symlink to lib subdir
+        self.assertTrue(os.path.exists(lib64_path))
         self.assertTrue(os.path.islink(lib64_path))
         self.assertTrue(os.path.samefile(lib_path, lib64_path))
-        # Need relative path: https://github.com/easybuilders/easybuild-framework/issues/3564
+
+        # lib64 symlink should point to a relative path
         self.assertFalse(os.path.isabs(os.readlink(lib64_path)))
 
         # cleanup and try again with --disable-lib64-lib-symlink
@@ -3164,6 +3168,49 @@ def test_test_toy_build_lib64_symlink(self):
         self.assertTrue(os.path.isdir(lib_path))
         self.assertFalse(os.path.islink(lib_path))
 
+    def test_test_toy_build_lib_lib64_symlink(self):
+        """Check whether lib64 symlink to lib subdirectory is created."""
+
+        test_ecs = os.path.join(os.path.dirname(__file__), 'easyconfigs', 'test_ecs')
+        toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')
+
+        test_ec_txt = read_file(toy_ec)
+        test_ec_txt += "\npostinstallcmds += ['mv %(installdir)s/lib %(installdir)s/lib64']"
+
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        write_file(test_ec, test_ec_txt)
+
+        # by default, lib -> lib64 symlink is created (--lib-lib64-symlink is enabled by default)
+        self.test_toy_build(ec_file=test_ec)
+
+        toy_installdir = os.path.join(self.test_installpath, 'software', 'toy', '0.0')
+        lib_path = os.path.join(toy_installdir, 'lib')
+        lib64_path = os.path.join(toy_installdir, 'lib64')
+
+        # lib64 subdir exists, is not a symlink
+        self.assertTrue(os.path.exists(lib64_path))
+        self.assertTrue(os.path.isdir(lib64_path))
+        self.assertFalse(os.path.islink(lib64_path))
+
+        # lib subdir is a symlink to lib64 subdir
+        self.assertTrue(os.path.exists(lib_path))
+        self.assertTrue(os.path.isdir(lib_path))
+        self.assertTrue(os.path.islink(lib_path))
+        self.assertTrue(os.path.samefile(lib_path, lib64_path))
+
+        # lib symlink should point to a relative path
+        self.assertFalse(os.path.isabs(os.readlink(lib_path)))
+
+        # cleanup and try again with --disable-lib-lib64-symlink
+        remove_dir(self.test_installpath)
+        self.test_toy_build(ec_file=test_ec, extra_args=['--disable-lib-lib64-symlink'])
+
+        self.assertTrue(os.path.exists(lib64_path))
+        self.assertFalse(os.path.exists(lib_path))
+        self.assertFalse('lib' in os.listdir(toy_installdir))
+        self.assertTrue(os.path.isdir(lib64_path))
+        self.assertFalse(os.path.islink(lib64_path))
+
 
 def suite():
     """ return all the tests in this file """