Repository: easybuilders/easybuild-framework

Issue:
unexpected behaviour with --set-default-module --robot
The EasyBuild option `--set-default-module` will apply to all the recipes in the same command line: this means that every dependency of the build using `--set-default-module` will be set default, overwriting existing  defaults for different versions of the same module name. I paste here the info on the flag:
```
eb -H | grep default
    --set-default-module
                        Set the generated module as default (def False)
```
This is happening frequently with `CMake`, since there are builds using different `CMake` versions.

I provide an example below with Bison, that depends on M4 (the lower case naming scheme is enabled on the build system and I skip lines of output using the symbol `[...]` to make it shorter): 
```
eb Bison-3.3.2.eb -Dr
[...]
 * [ ] $CFGS/m/M4/M4-1.4.18.eb (module: m4/1.4.18)
 * [ ] $CFGS/b/Bison/Bison-3.3.2.eb (module: bison/3.3.2)

eb Bison-3.3.2.eb -r --set-default-module
 [...]

module avail
------------------------------------------------------------------ 
bison/3.3.2(default)  m4/1.4.18(default)
```


Discussion:
Thanks for the bug report @lucamar!

It's certainly not the intention that `--set-default-module` applies to all dependencies, it should indeed only apply to the modules that correspond with the easyconfig files explicitly listed.

I'll try and get this fixed ASAP...

Resolving patch:
diff --git a/easybuild/framework/easyblock.py b/easybuild/framework/easyblock.py
--- a/easybuild/framework/easyblock.py
+++ b/easybuild/framework/easyblock.py
@@ -175,6 +175,7 @@ def __init__(self, ec):
         self.module_generator = module_generator(self, fake=True)
         self.mod_filepath = self.module_generator.get_module_filepath()
         self.mod_file_backup = None
+        self.set_default_module = self.cfg.set_default_module
 
         # modules footer/header
         self.modules_footer = None
@@ -2719,7 +2720,7 @@ def make_module_step(self, fake=False):
 
         # always set default for temporary module file,
         # to avoid that it gets overruled by an existing module file that is set as default
-        if fake or build_option('set_default_module'):
+        if fake or self.set_default_module:
             self._set_module_as_default(fake=fake)
 
         return modpath
diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py
--- a/easybuild/framework/easyconfig/easyconfig.py
+++ b/easybuild/framework/easyconfig/easyconfig.py
@@ -497,6 +497,8 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
         self.short_mod_name = mns.det_short_module_name(self)
         self.mod_subdir = mns.det_module_subdir(self)
 
+        self.set_default_module = False
+
         self.software_license = None
 
     def filename(self):
diff --git a/easybuild/main.py b/easybuild/main.py
--- a/easybuild/main.py
+++ b/easybuild/main.py
@@ -362,6 +362,11 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None):
                 print_msg("%s is already installed (module found), skipping" % skipped_ec['full_mod_name'])
         easyconfigs = retained_ecs
 
+    # keep track for which easyconfigs we should set the corresponding module as default
+    if options.set_default_module:
+        for easyconfig in easyconfigs:
+            easyconfig['ec'].set_default_module = True
+
     # determine an order that will allow all specs in the set to build
     if len(easyconfigs) > 0:
         # resolve dependencies if robot is enabled, except in dry run mode


Test patch:
diff --git a/test/framework/options.py b/test/framework/options.py
--- a/test/framework/options.py
+++ b/test/framework/options.py
@@ -3980,6 +3980,60 @@ def test_set_default_module(self):
         else:
             self.assertTrue(False, "Uknown module syntax: %s" % get_module_syntax())
 
+    def test_set_default_module_robot(self):
+        """Test use of --set-default-module --robot."""
+        # create two test easyconfigs, one depending on the other
+        # (using dummy Toolchain easyblock included in the tests)
+        test_ec = os.path.join(self.test_prefix, 'test.eb')
+        write_file(test_ec, '\n'.join([
+            "easyblock = 'Toolchain'",
+            "name = 'test'",
+            "version = '1.0'",
+            "homepage = 'https://example.com'",
+            "description = 'this is just a test'",
+            "toolchain = SYSTEM",
+            "dependencies = [('thisisjustatestdep', '3.14')]",
+        ]))
+        testdep_ec = os.path.join(self.test_prefix, 'thisisjustatestdep-3.14.eb')
+        write_file(testdep_ec, '\n'.join([
+            "easyblock = 'Toolchain'",
+            "name = 'thisisjustatestdep'",
+            "version = '3.14'",
+            "homepage = 'https://example.com'",
+            "description = 'this is just a test'",
+            "toolchain = SYSTEM",
+        ]))
+
+        args = [
+            test_ec,
+            '--force',
+            '--set-default-module',
+            '--robot',
+            self.test_prefix,
+        ]
+        self.eb_main(args, do_build=True, raise_error=True)
+
+        # default module is set for specified easyconfig, but *not* for its dependency
+        modfiles_dir = os.path.join(self.test_installpath, 'modules', 'all')
+        self.assertEqual(sorted(os.listdir(modfiles_dir)), ['test', 'thisisjustatestdep'])
+        test_mod_dir = os.path.join(modfiles_dir, 'test')
+        testdep_mod_dir = os.path.join(modfiles_dir, 'thisisjustatestdep')
+
+        if get_module_syntax() == 'Lua':
+            # only 'default' symlink for test/1.0, not for thisisjustadep/3.14
+            self.assertEqual(sorted(os.listdir(test_mod_dir)), ['1.0.lua', 'default'])
+            self.assertEqual(sorted(os.listdir(testdep_mod_dir)), ['3.14.lua'])
+            default_symlink = os.path.join(test_mod_dir, 'default')
+            self.assertTrue(os.path.islink(default_symlink))
+            self.assertEqual(os.readlink(default_symlink), '1.0.lua')
+        elif get_module_syntax() == 'Tcl':
+            self.assertEqual(sorted(os.listdir(test_mod_dir)), ['.version', '1.0'])
+            self.assertEqual(sorted(os.listdir(testdep_mod_dir)), ['3.14'])
+            dot_version_file = os.path.join(test_mod_dir, '.version')
+            self.assertTrue("set ModulesVersion 1.0" in read_file(dot_version_file))
+        else:
+            self.assertTrue(False, "Uknown module syntax: %s" % get_module_syntax())
+
     def test_inject_checksums(self):
         """Test for --inject-checksums"""
         topdir = os.path.dirname(os.path.abspath(__file__))