Repository: easybuilders/easybuild-easyblocks

Issue:
GROMACS easyblock does not work with `--module-only`
Trying to rebuild the module leads to
```
== configuring [skipped]
== building [skipped]
== testing [skipped]
== installing [skipped]
== taking care of extensions [skipped]
== restore after iterating...
== postprocessing [skipped]
== sanity checking...
ERROR: Traceback (most recent call last):
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/main.py", line 117, in build_and_install_software
    (ec_res['success'], app_log, err) = build_and_install_one(ec, init_env)
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/framework/easyblock.py", line 3412, in build_and_install_one
    result = app.run_all_steps(run_test_cases=run_test_cases)
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/easyblocks/g/gromacs.py", line 711, in run_all_steps
    return super(EB_GROMACS, self).run_all_steps(*args, **kwargs)
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/framework/easyblock.py", line 3311, in run_all_steps
    self.run_step(step_name, step_methods)
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/framework/easyblock.py", line 3166, in run_step
    step_method(self)()
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/easyblocks/g/gromacs.py", line 589, in sanity_check_step
    lib_files.extend([
  File "/project/def-sponsor00/ocaisa/easybuild/software/EasyBuild/4.3.4/lib/python3.8/site-packages/easybuild/easyblocks/g/gromacs.py", line 590, in <listcomp>
    'lib%s%s.%s' % (x, suff, self.libext) for x in libnames + mpi_libnames for suff in suffixes
AttributeError: 'EB_GROMACS' object has no attribute 'libext'
```


Discussion:
There will be a similar problem with `self.lib_subdir` (which is a little harder to fix)

Resolving patch:
diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py
--- a/easybuild/easyblocks/g/gromacs.py
+++ b/easybuild/easyblocks/g/gromacs.py
@@ -76,6 +76,13 @@ def __init__(self, *args, **kwargs):
         self.lib_subdir = ''
         self.pre_env = ''
         self.cfg['build_shared_libs'] = self.cfg.get('build_shared_libs', False)
+        if LooseVersion(self.version) >= LooseVersion('2019'):
+            # Building the gmxapi interface requires shared libraries
+            self.cfg['build_shared_libs'] = True
+        if self.cfg['build_shared_libs']:
+            self.libext = get_shared_lib_ext()
+        else:
+            self.libext = 'a'
 
     def get_gromacs_arch(self):
         """Determine value of GMX_SIMD CMake flag based on optarch string.
@@ -269,8 +276,8 @@ def configure_step(self):
                               mpi_numprocs)
 
             if LooseVersion(self.version) >= LooseVersion('2019'):
-                # Building the gmxapi interface requires shared libraries
-                self.cfg['build_shared_libs'] = True
+                # Building the gmxapi interface requires shared libraries,
+                # this is handled in the class initialisation so --module-only works
                 self.cfg.update('configopts', "-DGMXAPI=ON")
 
                 if LooseVersion(self.version) >= LooseVersion('2020'):
@@ -463,35 +470,8 @@ def install_step(self):
         else:
             # run 'make install' in parallel since it involves more compilation
             self.cfg.update('installopts', "-j %s" % self.cfg['parallel'])
-
             super(EB_GROMACS, self).install_step()
 
-            # the GROMACS libraries get installed in different locations (deeper subdirectory),
-            # depending on the platform;
-            # this is determined by the GNUInstallDirs CMake module;
-            # rather than trying to replicate the logic, we just figure out where the library was placed
-
-            if self.cfg['build_shared_libs']:
-                self.libext = get_shared_lib_ext()
-            else:
-                self.libext = 'a'
-
-            if LooseVersion(self.version) < LooseVersion('5.0'):
-                libname = 'libgmx*.%s' % self.libext
-            else:
-                libname = 'libgromacs*.%s' % self.libext
-
-            for libdir in ['lib', 'lib64']:
-                if os.path.exists(os.path.join(self.installdir, libdir)):
-                    for subdir in [libdir, os.path.join(libdir, '*')]:
-                        libpaths = glob.glob(os.path.join(self.installdir, subdir, libname))
-                        if libpaths:
-                            self.lib_subdir = os.path.dirname(libpaths[0])[len(self.installdir) + 1:]
-                            self.log.info("Found lib subdirectory that contains %s: %s", libname, self.lib_subdir)
-                            break
-            if not self.lib_subdir:
-                raise EasyBuildError("Failed to determine lib subdirectory in %s", self.installdir)
-
     def extensions_step(self, fetch=False):
         """ Custom extensions step, only handle extensions after the last iteration round"""
         if self.iter_idx < self.variants_to_build - 1:
@@ -508,9 +488,42 @@ def extensions_step(self, fetch=False):
             super(EB_GROMACS, self).extensions_step(fetch)
             self.cfg['runtest'] = orig_runtest
 
+    def get_lib_subdir(self):
+        # the GROMACS libraries get installed in different locations (deeper subdirectory),
+        # depending on the platform;
+        # this is determined by the GNUInstallDirs CMake module;
+        # rather than trying to replicate the logic, we just figure out where the library was placed
+
+        if LooseVersion(self.version) < LooseVersion('5.0'):
+            libname = 'libgmx*.%s' % self.libext
+        else:
+            libname = 'libgromacs*.%s' % self.libext
+        lib_subdir = None
+        for libdir in ['lib', 'lib64']:
+            if os.path.exists(os.path.join(self.installdir, libdir)):
+                for subdir in [libdir, os.path.join(libdir, '*')]:
+                    libpaths = glob.glob(os.path.join(self.installdir, subdir, libname))
+                    if libpaths:
+                        lib_subdir = os.path.dirname(libpaths[0])[len(self.installdir) + 1:]
+                        self.log.info("Found lib subdirectory that contains %s: %s", libname, lib_subdir)
+                        break
+        if not lib_subdir:
+            raise EasyBuildError("Failed to determine lib subdirectory in %s", self.installdir)
+
+        return lib_subdir
+
     def make_module_req_guess(self):
         """Custom library subdirectories for GROMACS."""
         guesses = super(EB_GROMACS, self).make_module_req_guess()
+        if not self.lib_subdir:
+            try:
+                self.lib_subdir = self.get_lib_subdir()
+            except EasyBuildError as error:
+                if build_option('force') and build_option('module_only'):
+                    self.log.info("No lib subdirectory directory found in installation: %s", error)
+                    self.log.info("You are forcing module creation for a non-existent installation!")
+                else:
+                    raise error
         guesses.update({
             'LD_LIBRARY_PATH': [self.lib_subdir],
             'LIBRARY_PATH': [self.lib_subdir],
@@ -592,6 +605,9 @@ def sanity_check_step(self):
         ])
         bin_files.extend([b + suff for b in bins + mpi_bins for suff in suffixes])
 
+        if not self.lib_subdir:
+            self.lib_subdir = self.get_lib_subdir()
+
         # pkgconfig dir not available for earlier versions, exact version to use here is unclear
         if LooseVersion(self.version) >= LooseVersion('4.6'):
             dirs.append(os.path.join(self.lib_subdir, 'pkgconfig'))


Test patch: