We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
--module-only does not respect altroot
In `CUDA-11.1.1-GCC-10.2.0.eb` we have `altroot = 'CUDAcore'`. When I build the module (`eb CUDA-11.1.1-GCC-10.2.0.eb -Tr --rebuild`) I get:
```
setenv("EBROOTCUDA", "/rds/bear-apps/devel/eb-sjb-up/EL8/EL8-has/software/CUDAcore/11.1.1")
```
but when I add ` --module-only` this is replaced with:
```
setenv("EBROOTCUDA", root)
```
and `root` is set to `/rds/bear-apps/devel/eb-sjb-up/EL8/EL8-has/software/CUDA/11.1.1-GCC-10.2.0`.

This then breaks any software that uses `get_software_root('CUDA')` to determine the location of the CUDA files. For example, TensorFlow which fails to find `cublas_api.h` and so aborts the install.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `easybuild/easyblocks/generic/bundle.py`
Content:
```
1 ##
2 # Copyright 2009-2021 Ghent University
3 #
4 # This file is part of EasyBuild,
5 # originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
6 # with support of Ghent University (http://ugent.be/hpc),
7 # the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
8 # Flemish Research Foundation (FWO) (http://www.fwo.be/en)
9 # and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
10 #
11 # https://github.com/easybuilders/easybuild
12 #
13 # EasyBuild is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation v2.
16 #
17 # EasyBuild is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
24 ##
25 """
26 EasyBuild support for installing a bundle of modules, implemented as a generic easyblock
27 
28 @author: Stijn De Weirdt (Ghent University)
29 @author: Dries Verdegem (Ghent University)
30 @author: Kenneth Hoste (Ghent University)
31 @author: Pieter De Baets (Ghent University)
32 @author: Jens Timmerman (Ghent University)
33 """
34 import copy
35 import os
36 
37 import easybuild.tools.environment as env
38 from easybuild.framework.easyblock import EasyBlock
39 from easybuild.framework.easyconfig import CUSTOM
40 from easybuild.framework.easyconfig.easyconfig import get_easyblock_class
41 from easybuild.tools.build_log import EasyBuildError, print_msg
42 from easybuild.tools.modules import get_software_root, get_software_version
43 from easybuild.tools.py2vs3 import string_type
44 
45 
46 class Bundle(EasyBlock):
47     """
48     Bundle of modules: only generate module files, nothing to build/install
49     """
50 
51     @staticmethod
52     def extra_options(extra_vars=None):
53         """Easyconfig parameters specific to bundles."""
54         if extra_vars is None:
55             extra_vars = {}
56         extra_vars.update({
57             'altroot': [None, "Software name of dependency to use to define $EBROOT for this bundle", CUSTOM],
58             'altversion': [None, "Software name of dependency to use to define $EBVERSION for this bundle", CUSTOM],
59             'default_component_specs': [{}, "Default specs to use for every component", CUSTOM],
60             'components': [(), "List of components to install: tuples w/ name, version and easyblock to use", CUSTOM],
61             'default_easyblock': [None, "Default easyblock to use for components", CUSTOM],
62         })
63         return EasyBlock.extra_options(extra_vars)
64 
65     def __init__(self, *args, **kwargs):
66         """Initialize easyblock."""
67         super(Bundle, self).__init__(*args, **kwargs)
68         self.altroot = None
69         self.altversion = None
70 
71         # list of EasyConfig instances for components
72         self.comp_cfgs = []
73 
74         # list of sources for bundle itself *must* be empty
75         if self.cfg['sources']:
76             raise EasyBuildError("List of sources for bundle itself must be empty, found %s", self.cfg['sources'])
77         if self.cfg['patches']:
78             raise EasyBuildError("List of patches for bundle itself must be empty, found %s", self.cfg['patches'])
79 
80         # disable templating to avoid premature resolving of template values
81         self.cfg.enable_templating = False
82 
83         # list of checksums for patches (must be included after checksums for sources)
84         checksums_patches = []
85 
86         for comp in self.cfg['components']:
87             comp_name, comp_version, comp_specs = comp[0], comp[1], {}
88             if len(comp) == 3:
89                 comp_specs = comp[2]
90 
91             comp_cfg = self.cfg.copy()
92 
93             comp_cfg['name'] = comp_name
94             comp_cfg['version'] = comp_version
95 
96             easyblock = comp_specs.get('easyblock') or self.cfg['default_easyblock']
97             if easyblock is None:
98                 raise EasyBuildError("No easyblock specified for component %s v%s", comp_cfg['name'],
99                                      comp_cfg['version'])
100             elif easyblock == 'Bundle':
101                 raise EasyBuildError("The Bundle easyblock can not be used to install components in a bundle")
102 
103             comp_cfg.easyblock = get_easyblock_class(easyblock, name=comp_cfg['name'])
104 
105             # make sure that extra easyconfig parameters are known, so they can be set
106             extra_opts = comp_cfg.easyblock.extra_options()
107             comp_cfg.extend_params(copy.deepcopy(extra_opts))
108 
109             comp_cfg.generate_template_values()
110 
111             # do not inherit easyblock to use from parent (since that would result in an infinite loop in install_step)
112             comp_cfg['easyblock'] = None
113 
114             # reset list of sources/source_urls/checksums
115             comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = comp_cfg['patches'] = []
116 
117             for key in self.cfg['default_component_specs']:
118                 comp_cfg[key] = self.cfg['default_component_specs'][key]
119 
120             for key in comp_specs:
121                 comp_cfg[key] = comp_specs[key]
122 
123             # enable resolving of templates for component-specific EasyConfig instance
124             comp_cfg.enable_templating = True
125 
126             # 'sources' is strictly required
127             if comp_cfg['sources']:
128                 # If per-component source URLs are provided, attach them directly to the relevant sources
129                 if comp_cfg['source_urls']:
130                     for source in comp_cfg['sources']:
131                         if isinstance(source, string_type):
132                             self.cfg.update('sources', [{'filename': source, 'source_urls': comp_cfg['source_urls']}])
133                         elif isinstance(source, dict):
134                             # Update source_urls in the 'source' dict to use the one for the components
135                             # (if it doesn't already exist)
136                             if 'source_urls' not in source:
137                                 source['source_urls'] = comp_cfg['source_urls']
138                             self.cfg.update('sources', [source])
139                         else:
140                             raise EasyBuildError("Source %s for component %s is neither a string nor a dict, cannot "
141                                                  "process it.", source, comp_cfg['name'])
142                 else:
143                     # add component sources to list of sources
144                     self.cfg.update('sources', comp_cfg['sources'])
145             else:
146                 raise EasyBuildError("No sources specification for component %s v%s", comp_name, comp_version)
147 
148             if comp_cfg['checksums']:
149                 src_cnt = len(comp_cfg['sources'])
150 
151                 # add per-component checksums for sources to list of checksums
152                 self.cfg.update('checksums', comp_cfg['checksums'][:src_cnt])
153 
154                 # add per-component checksums for patches to list of checksums for patches
155                 checksums_patches.extend(comp_cfg['checksums'][src_cnt:])
156 
157             if comp_cfg['patches']:
158                 self.cfg.update('patches', comp_cfg['patches'])
159 
160             self.comp_cfgs.append(comp_cfg)
161 
162         self.cfg.update('checksums', checksums_patches)
163 
164         self.cfg.enable_templating = True
165 
166     def check_checksums(self):
167         """
168         Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
169 
170         :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
171         """
172         checksum_issues = super(Bundle, self).check_checksums()
173 
174         for comp in self.comp_cfgs:
175             checksum_issues.extend(self.check_checksums_for(comp, sub="of component %s" % comp['name']))
176 
177         return checksum_issues
178 
179     def patch_step(self):
180         """Patch step must be a no-op for bundle, since there are no top-level sources/patches."""
181         pass
182 
183     def configure_step(self):
184         """Collect altroot/altversion info."""
185         # pick up altroot/altversion, if they are defined
186         self.altroot = None
187         if self.cfg['altroot']:
188             self.altroot = get_software_root(self.cfg['altroot'])
189         self.altversion = None
190         if self.cfg['altversion']:
191             self.altversion = get_software_version(self.cfg['altversion'])
192 
193     def build_step(self):
194         """Do nothing."""
195         pass
196 
197     def install_step(self):
198         """Install components, if specified."""
199         comp_cnt = len(self.cfg['components'])
200         for idx, cfg in enumerate(self.comp_cfgs):
201 
202             print_msg("installing bundle component %s v%s (%d/%d)..." %
203                       (cfg['name'], cfg['version'], idx + 1, comp_cnt))
204             self.log.info("Installing component %s v%s using easyblock %s", cfg['name'], cfg['version'], cfg.easyblock)
205 
206             comp = cfg.easyblock(cfg)
207 
208             # correct build/install dirs
209             comp.builddir = self.builddir
210             comp.install_subdir, comp.installdir = self.install_subdir, self.installdir
211 
212             # make sure we can build in parallel
213             comp.set_parallel()
214 
215             # figure out correct start directory
216             comp.guess_start_dir()
217 
218             # need to run fetch_patches to ensure per-component patches are applied
219             comp.fetch_patches()
220 
221             comp.src = []
222 
223             # find match entries in self.src for this component
224             for source in comp.cfg['sources']:
225                 if isinstance(source, string_type):
226                     comp_src_fn = source
227                 elif isinstance(source, dict):
228                     if 'filename' in source:
229                         comp_src_fn = source['filename']
230                     else:
231                         raise EasyBuildError("Encountered source file specified as dict without 'filename': %s", source)
232                 else:
233                     raise EasyBuildError("Specification of unknown type for source file: %s", source)
234 
235                 found = False
236                 for src in self.src:
237                     if src['name'] == comp_src_fn:
238                         self.log.info("Found spec for source %s for component %s: %s", comp_src_fn, comp.name, src)
239                         comp.src.append(src)
240                         found = True
241                         break
242                 if not found:
243                     raise EasyBuildError("Failed to find spec for source %s for component %s", comp_src_fn, comp.name)
244 
245                 # location of first unpacked source is used to determine where to apply patch(es)
246                 comp.src[-1]['finalpath'] = comp.cfg['start_dir']
247 
248             # run relevant steps
249             for step_name in ['patch', 'configure', 'build', 'install']:
250                 if step_name in cfg['skipsteps']:
251                     comp.log.info("Skipping '%s' step for component %s v%s", step_name, cfg['name'], cfg['version'])
252                 else:
253                     comp.run_step(step_name, [lambda x: getattr(x, '%s_step' % step_name)])
254 
255             # update environment to ensure stuff provided by former components can be picked up by latter components
256             # once the installation is finalised, this is handled by the generated module
257             reqs = comp.make_module_req_guess()
258             for envvar in reqs:
259                 curr_val = os.getenv(envvar, '')
260                 curr_paths = curr_val.split(os.pathsep)
261                 for subdir in reqs[envvar]:
262                     path = os.path.join(self.installdir, subdir)
263                     if path not in curr_paths:
264                         if curr_val:
265                             new_val = '%s:%s' % (path, curr_val)
266                         else:
267                             new_val = path
268                         env.setvar(envvar, new_val)
269 
270     def make_module_extra(self, *args, **kwargs):
271         """Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc."""
272         if 'altroot' not in kwargs:
273             kwargs['altroot'] = self.altroot
274         if 'altversion' not in kwargs:
275             kwargs['altversion'] = self.altversion
276         return super(Bundle, self).make_module_extra(*args, **kwargs)
277 
278     def sanity_check_step(self, *args, **kwargs):
279         """
280         Nothing is being installed, so just being able to load the (fake) module is sufficient
281         """
282         if self.cfg['exts_list'] or self.cfg['sanity_check_paths'] or self.cfg['sanity_check_commands']:
283             super(Bundle, self).sanity_check_step(*args, **kwargs)
284         else:
285             self.log.info("Testing loading of module '%s' by means of sanity check" % self.full_mod_name)
286             fake_mod_data = self.load_fake_module(purge=True)
287             self.log.debug("Cleaning up after testing loading of module")
288             self.clean_up_fake_module(fake_mod_data)
289
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/easybuild/easyblocks/generic/bundle.py b/easybuild/easyblocks/generic/bundle.py
--- a/easybuild/easyblocks/generic/bundle.py
+++ b/easybuild/easyblocks/generic/bundle.py
@@ -180,15 +180,19 @@
         """Patch step must be a no-op for bundle, since there are no top-level sources/patches."""
         pass
 
-    def configure_step(self):
-        """Collect altroot/altversion info."""
-        # pick up altroot/altversion, if they are defined
-        self.altroot = None
+    def get_altroot_and_altversion(self):
+        """Get altroot and altversion, if they are defined"""
+        altroot = None
         if self.cfg['altroot']:
-            self.altroot = get_software_root(self.cfg['altroot'])
-        self.altversion = None
+            altroot = get_software_root(self.cfg['altroot'])
+        altversion = None
         if self.cfg['altversion']:
-            self.altversion = get_software_version(self.cfg['altversion'])
+            altversion = get_software_version(self.cfg['altversion'])
+        return altroot, altversion
+
+    def configure_step(self):
+        """Collect altroot/altversion info."""
+        self.altroot, self.altversion = self.get_altroot_and_altversion()
 
     def build_step(self):
         """Do nothing."""
@@ -269,6 +273,9 @@
 
     def make_module_extra(self, *args, **kwargs):
         """Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc."""
+        if not self.altroot and not self.altversion:
+            # check for altroot and altversion (needed here for a module only build)
+            self.altroot, self.altversion = self.get_altroot_and_altversion()
         if 'altroot' not in kwargs:
             kwargs['altroot'] = self.altroot
         if 'altversion' not in kwargs:

{"golden_diff": "diff --git a/easybuild/easyblocks/generic/bundle.py b/easybuild/easyblocks/generic/bundle.py\n--- a/easybuild/easyblocks/generic/bundle.py\n+++ b/easybuild/easyblocks/generic/bundle.py\n@@ -180,15 +180,19 @@\n         \"\"\"Patch step must be a no-op for bundle, since there are no top-level sources/patches.\"\"\"\n         pass\n \n-    def configure_step(self):\n-        \"\"\"Collect altroot/altversion info.\"\"\"\n-        # pick up altroot/altversion, if they are defined\n-        self.altroot = None\n+    def get_altroot_and_altversion(self):\n+        \"\"\"Get altroot and altversion, if they are defined\"\"\"\n+        altroot = None\n         if self.cfg['altroot']:\n-            self.altroot = get_software_root(self.cfg['altroot'])\n-        self.altversion = None\n+            altroot = get_software_root(self.cfg['altroot'])\n+        altversion = None\n         if self.cfg['altversion']:\n-            self.altversion = get_software_version(self.cfg['altversion'])\n+            altversion = get_software_version(self.cfg['altversion'])\n+        return altroot, altversion\n+\n+    def configure_step(self):\n+        \"\"\"Collect altroot/altversion info.\"\"\"\n+        self.altroot, self.altversion = self.get_altroot_and_altversion()\n \n     def build_step(self):\n         \"\"\"Do nothing.\"\"\"\n@@ -269,6 +273,9 @@\n \n     def make_module_extra(self, *args, **kwargs):\n         \"\"\"Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc.\"\"\"\n+        if not self.altroot and not self.altversion:\n+            # check for altroot and altversion (needed here for a module only build)\n+            self.altroot, self.altversion = self.get_altroot_and_altversion()\n         if 'altroot' not in kwargs:\n             kwargs['altroot'] = self.altroot\n         if 'altversion' not in kwargs:\n", "issue": "--module-only does not respect altroot\nIn `CUDA-11.1.1-GCC-10.2.0.eb` we have `altroot = 'CUDAcore'`. When I build the module (`eb CUDA-11.1.1-GCC-10.2.0.eb -Tr --rebuild`) I get:\r\n```\r\nsetenv(\"EBROOTCUDA\", \"/rds/bear-apps/devel/eb-sjb-up/EL8/EL8-has/software/CUDAcore/11.1.1\")\r\n```\r\nbut when I add ` --module-only` this is replaced with:\r\n```\r\nsetenv(\"EBROOTCUDA\", root)\r\n```\r\nand `root` is set to `/rds/bear-apps/devel/eb-sjb-up/EL8/EL8-has/software/CUDA/11.1.1-GCC-10.2.0`.\r\n\r\nThis then breaks any software that uses `get_software_root('CUDA')` to determine the location of the CUDA files. For example, TensorFlow which fails to find `cublas_api.h` and so aborts the install.\n", "before_files": [{"content": "##\n# Copyright 2009-2021 Ghent University\n#\n# This file is part of EasyBuild,\n# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),\n# with support of Ghent University (http://ugent.be/hpc),\n# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),\n# Flemish Research Foundation (FWO) (http://www.fwo.be/en)\n# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).\n#\n# https://github.com/easybuilders/easybuild\n#\n# EasyBuild is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation v2.\n#\n# EasyBuild is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.\n##\n\"\"\"\nEasyBuild support for installing a bundle of modules, implemented as a generic easyblock\n\n@author: Stijn De Weirdt (Ghent University)\n@author: Dries Verdegem (Ghent University)\n@author: Kenneth Hoste (Ghent University)\n@author: Pieter De Baets (Ghent University)\n@author: Jens Timmerman (Ghent University)\n\"\"\"\nimport copy\nimport os\n\nimport easybuild.tools.environment as env\nfrom easybuild.framework.easyblock import EasyBlock\nfrom easybuild.framework.easyconfig import CUSTOM\nfrom easybuild.framework.easyconfig.easyconfig import get_easyblock_class\nfrom easybuild.tools.build_log import EasyBuildError, print_msg\nfrom easybuild.tools.modules import get_software_root, get_software_version\nfrom easybuild.tools.py2vs3 import string_type\n\n\nclass Bundle(EasyBlock):\n    \"\"\"\n    Bundle of modules: only generate module files, nothing to build/install\n    \"\"\"\n\n    @staticmethod\n    def extra_options(extra_vars=None):\n        \"\"\"Easyconfig parameters specific to bundles.\"\"\"\n        if extra_vars is None:\n            extra_vars = {}\n        extra_vars.update({\n            'altroot': [None, \"Software name of dependency to use to define $EBROOT for this bundle\", CUSTOM],\n            'altversion': [None, \"Software name of dependency to use to define $EBVERSION for this bundle\", CUSTOM],\n            'default_component_specs': [{}, \"Default specs to use for every component\", CUSTOM],\n            'components': [(), \"List of components to install: tuples w/ name, version and easyblock to use\", CUSTOM],\n            'default_easyblock': [None, \"Default easyblock to use for components\", CUSTOM],\n        })\n        return EasyBlock.extra_options(extra_vars)\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Initialize easyblock.\"\"\"\n        super(Bundle, self).__init__(*args, **kwargs)\n        self.altroot = None\n        self.altversion = None\n\n        # list of EasyConfig instances for components\n        self.comp_cfgs = []\n\n        # list of sources for bundle itself *must* be empty\n        if self.cfg['sources']:\n            raise EasyBuildError(\"List of sources for bundle itself must be empty, found %s\", self.cfg['sources'])\n        if self.cfg['patches']:\n            raise EasyBuildError(\"List of patches for bundle itself must be empty, found %s\", self.cfg['patches'])\n\n        # disable templating to avoid premature resolving of template values\n        self.cfg.enable_templating = False\n\n        # list of checksums for patches (must be included after checksums for sources)\n        checksums_patches = []\n\n        for comp in self.cfg['components']:\n            comp_name, comp_version, comp_specs = comp[0], comp[1], {}\n            if len(comp) == 3:\n                comp_specs = comp[2]\n\n            comp_cfg = self.cfg.copy()\n\n            comp_cfg['name'] = comp_name\n            comp_cfg['version'] = comp_version\n\n            easyblock = comp_specs.get('easyblock') or self.cfg['default_easyblock']\n            if easyblock is None:\n                raise EasyBuildError(\"No easyblock specified for component %s v%s\", comp_cfg['name'],\n                                     comp_cfg['version'])\n            elif easyblock == 'Bundle':\n                raise EasyBuildError(\"The Bundle easyblock can not be used to install components in a bundle\")\n\n            comp_cfg.easyblock = get_easyblock_class(easyblock, name=comp_cfg['name'])\n\n            # make sure that extra easyconfig parameters are known, so they can be set\n            extra_opts = comp_cfg.easyblock.extra_options()\n            comp_cfg.extend_params(copy.deepcopy(extra_opts))\n\n            comp_cfg.generate_template_values()\n\n            # do not inherit easyblock to use from parent (since that would result in an infinite loop in install_step)\n            comp_cfg['easyblock'] = None\n\n            # reset list of sources/source_urls/checksums\n            comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = comp_cfg['patches'] = []\n\n            for key in self.cfg['default_component_specs']:\n                comp_cfg[key] = self.cfg['default_component_specs'][key]\n\n            for key in comp_specs:\n                comp_cfg[key] = comp_specs[key]\n\n            # enable resolving of templates for component-specific EasyConfig instance\n            comp_cfg.enable_templating = True\n\n            # 'sources' is strictly required\n            if comp_cfg['sources']:\n                # If per-component source URLs are provided, attach them directly to the relevant sources\n                if comp_cfg['source_urls']:\n                    for source in comp_cfg['sources']:\n                        if isinstance(source, string_type):\n                            self.cfg.update('sources', [{'filename': source, 'source_urls': comp_cfg['source_urls']}])\n                        elif isinstance(source, dict):\n                            # Update source_urls in the 'source' dict to use the one for the components\n                            # (if it doesn't already exist)\n                            if 'source_urls' not in source:\n                                source['source_urls'] = comp_cfg['source_urls']\n                            self.cfg.update('sources', [source])\n                        else:\n                            raise EasyBuildError(\"Source %s for component %s is neither a string nor a dict, cannot \"\n                                                 \"process it.\", source, comp_cfg['name'])\n                else:\n                    # add component sources to list of sources\n                    self.cfg.update('sources', comp_cfg['sources'])\n            else:\n                raise EasyBuildError(\"No sources specification for component %s v%s\", comp_name, comp_version)\n\n            if comp_cfg['checksums']:\n                src_cnt = len(comp_cfg['sources'])\n\n                # add per-component checksums for sources to list of checksums\n                self.cfg.update('checksums', comp_cfg['checksums'][:src_cnt])\n\n                # add per-component checksums for patches to list of checksums for patches\n                checksums_patches.extend(comp_cfg['checksums'][src_cnt:])\n\n            if comp_cfg['patches']:\n                self.cfg.update('patches', comp_cfg['patches'])\n\n            self.comp_cfgs.append(comp_cfg)\n\n        self.cfg.update('checksums', checksums_patches)\n\n        self.cfg.enable_templating = True\n\n    def check_checksums(self):\n        \"\"\"\n        Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).\n\n        :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)\n        \"\"\"\n        checksum_issues = super(Bundle, self).check_checksums()\n\n        for comp in self.comp_cfgs:\n            checksum_issues.extend(self.check_checksums_for(comp, sub=\"of component %s\" % comp['name']))\n\n        return checksum_issues\n\n    def patch_step(self):\n        \"\"\"Patch step must be a no-op for bundle, since there are no top-level sources/patches.\"\"\"\n        pass\n\n    def configure_step(self):\n        \"\"\"Collect altroot/altversion info.\"\"\"\n        # pick up altroot/altversion, if they are defined\n        self.altroot = None\n        if self.cfg['altroot']:\n            self.altroot = get_software_root(self.cfg['altroot'])\n        self.altversion = None\n        if self.cfg['altversion']:\n            self.altversion = get_software_version(self.cfg['altversion'])\n\n    def build_step(self):\n        \"\"\"Do nothing.\"\"\"\n        pass\n\n    def install_step(self):\n        \"\"\"Install components, if specified.\"\"\"\n        comp_cnt = len(self.cfg['components'])\n        for idx, cfg in enumerate(self.comp_cfgs):\n\n            print_msg(\"installing bundle component %s v%s (%d/%d)...\" %\n                      (cfg['name'], cfg['version'], idx + 1, comp_cnt))\n            self.log.info(\"Installing component %s v%s using easyblock %s\", cfg['name'], cfg['version'], cfg.easyblock)\n\n            comp = cfg.easyblock(cfg)\n\n            # correct build/install dirs\n            comp.builddir = self.builddir\n            comp.install_subdir, comp.installdir = self.install_subdir, self.installdir\n\n            # make sure we can build in parallel\n            comp.set_parallel()\n\n            # figure out correct start directory\n            comp.guess_start_dir()\n\n            # need to run fetch_patches to ensure per-component patches are applied\n            comp.fetch_patches()\n\n            comp.src = []\n\n            # find match entries in self.src for this component\n            for source in comp.cfg['sources']:\n                if isinstance(source, string_type):\n                    comp_src_fn = source\n                elif isinstance(source, dict):\n                    if 'filename' in source:\n                        comp_src_fn = source['filename']\n                    else:\n                        raise EasyBuildError(\"Encountered source file specified as dict without 'filename': %s\", source)\n                else:\n                    raise EasyBuildError(\"Specification of unknown type for source file: %s\", source)\n\n                found = False\n                for src in self.src:\n                    if src['name'] == comp_src_fn:\n                        self.log.info(\"Found spec for source %s for component %s: %s\", comp_src_fn, comp.name, src)\n                        comp.src.append(src)\n                        found = True\n                        break\n                if not found:\n                    raise EasyBuildError(\"Failed to find spec for source %s for component %s\", comp_src_fn, comp.name)\n\n                # location of first unpacked source is used to determine where to apply patch(es)\n                comp.src[-1]['finalpath'] = comp.cfg['start_dir']\n\n            # run relevant steps\n            for step_name in ['patch', 'configure', 'build', 'install']:\n                if step_name in cfg['skipsteps']:\n                    comp.log.info(\"Skipping '%s' step for component %s v%s\", step_name, cfg['name'], cfg['version'])\n                else:\n                    comp.run_step(step_name, [lambda x: getattr(x, '%s_step' % step_name)])\n\n            # update environment to ensure stuff provided by former components can be picked up by latter components\n            # once the installation is finalised, this is handled by the generated module\n            reqs = comp.make_module_req_guess()\n            for envvar in reqs:\n                curr_val = os.getenv(envvar, '')\n                curr_paths = curr_val.split(os.pathsep)\n                for subdir in reqs[envvar]:\n                    path = os.path.join(self.installdir, subdir)\n                    if path not in curr_paths:\n                        if curr_val:\n                            new_val = '%s:%s' % (path, curr_val)\n                        else:\n                            new_val = path\n                        env.setvar(envvar, new_val)\n\n    def make_module_extra(self, *args, **kwargs):\n        \"\"\"Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc.\"\"\"\n        if 'altroot' not in kwargs:\n            kwargs['altroot'] = self.altroot\n        if 'altversion' not in kwargs:\n            kwargs['altversion'] = self.altversion\n        return super(Bundle, self).make_module_extra(*args, **kwargs)\n\n    def sanity_check_step(self, *args, **kwargs):\n        \"\"\"\n        Nothing is being installed, so just being able to load the (fake) module is sufficient\n        \"\"\"\n        if self.cfg['exts_list'] or self.cfg['sanity_check_paths'] or self.cfg['sanity_check_commands']:\n            super(Bundle, self).sanity_check_step(*args, **kwargs)\n        else:\n            self.log.info(\"Testing loading of module '%s' by means of sanity check\" % self.full_mod_name)\n            fake_mod_data = self.load_fake_module(purge=True)\n            self.log.debug(\"Cleaning up after testing loading of module\")\n            self.clean_up_fake_module(fake_mod_data)\n", "path": "easybuild/easyblocks/generic/bundle.py"}], "after_files": [{"content": "##\n# Copyright 2009-2021 Ghent University\n#\n# This file is part of EasyBuild,\n# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),\n# with support of Ghent University (http://ugent.be/hpc),\n# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),\n# Flemish Research Foundation (FWO) (http://www.fwo.be/en)\n# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).\n#\n# https://github.com/easybuilders/easybuild\n#\n# EasyBuild is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation v2.\n#\n# EasyBuild is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.\n##\n\"\"\"\nEasyBuild support for installing a bundle of modules, implemented as a generic easyblock\n\n@author: Stijn De Weirdt (Ghent University)\n@author: Dries Verdegem (Ghent University)\n@author: Kenneth Hoste (Ghent University)\n@author: Pieter De Baets (Ghent University)\n@author: Jens Timmerman (Ghent University)\n\"\"\"\nimport copy\nimport os\n\nimport easybuild.tools.environment as env\nfrom easybuild.framework.easyblock import EasyBlock\nfrom easybuild.framework.easyconfig import CUSTOM\nfrom easybuild.framework.easyconfig.easyconfig import get_easyblock_class\nfrom easybuild.tools.build_log import EasyBuildError, print_msg\nfrom easybuild.tools.modules import get_software_root, get_software_version\nfrom easybuild.tools.py2vs3 import string_type\n\n\nclass Bundle(EasyBlock):\n    \"\"\"\n    Bundle of modules: only generate module files, nothing to build/install\n    \"\"\"\n\n    @staticmethod\n    def extra_options(extra_vars=None):\n        \"\"\"Easyconfig parameters specific to bundles.\"\"\"\n        if extra_vars is None:\n            extra_vars = {}\n        extra_vars.update({\n            'altroot': [None, \"Software name of dependency to use to define $EBROOT for this bundle\", CUSTOM],\n            'altversion': [None, \"Software name of dependency to use to define $EBVERSION for this bundle\", CUSTOM],\n            'default_component_specs': [{}, \"Default specs to use for every component\", CUSTOM],\n            'components': [(), \"List of components to install: tuples w/ name, version and easyblock to use\", CUSTOM],\n            'default_easyblock': [None, \"Default easyblock to use for components\", CUSTOM],\n        })\n        return EasyBlock.extra_options(extra_vars)\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Initialize easyblock.\"\"\"\n        super(Bundle, self).__init__(*args, **kwargs)\n        self.altroot = None\n        self.altversion = None\n\n        # list of EasyConfig instances for components\n        self.comp_cfgs = []\n\n        # list of sources for bundle itself *must* be empty\n        if self.cfg['sources']:\n            raise EasyBuildError(\"List of sources for bundle itself must be empty, found %s\", self.cfg['sources'])\n        if self.cfg['patches']:\n            raise EasyBuildError(\"List of patches for bundle itself must be empty, found %s\", self.cfg['patches'])\n\n        # disable templating to avoid premature resolving of template values\n        self.cfg.enable_templating = False\n\n        # list of checksums for patches (must be included after checksums for sources)\n        checksums_patches = []\n\n        for comp in self.cfg['components']:\n            comp_name, comp_version, comp_specs = comp[0], comp[1], {}\n            if len(comp) == 3:\n                comp_specs = comp[2]\n\n            comp_cfg = self.cfg.copy()\n\n            comp_cfg['name'] = comp_name\n            comp_cfg['version'] = comp_version\n\n            easyblock = comp_specs.get('easyblock') or self.cfg['default_easyblock']\n            if easyblock is None:\n                raise EasyBuildError(\"No easyblock specified for component %s v%s\", comp_cfg['name'],\n                                     comp_cfg['version'])\n            elif easyblock == 'Bundle':\n                raise EasyBuildError(\"The Bundle easyblock can not be used to install components in a bundle\")\n\n            comp_cfg.easyblock = get_easyblock_class(easyblock, name=comp_cfg['name'])\n\n            # make sure that extra easyconfig parameters are known, so they can be set\n            extra_opts = comp_cfg.easyblock.extra_options()\n            comp_cfg.extend_params(copy.deepcopy(extra_opts))\n\n            comp_cfg.generate_template_values()\n\n            # do not inherit easyblock to use from parent (since that would result in an infinite loop in install_step)\n            comp_cfg['easyblock'] = None\n\n            # reset list of sources/source_urls/checksums\n            comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = comp_cfg['patches'] = []\n\n            for key in self.cfg['default_component_specs']:\n                comp_cfg[key] = self.cfg['default_component_specs'][key]\n\n            for key in comp_specs:\n                comp_cfg[key] = comp_specs[key]\n\n            # enable resolving of templates for component-specific EasyConfig instance\n            comp_cfg.enable_templating = True\n\n            # 'sources' is strictly required\n            if comp_cfg['sources']:\n                # If per-component source URLs are provided, attach them directly to the relevant sources\n                if comp_cfg['source_urls']:\n                    for source in comp_cfg['sources']:\n                        if isinstance(source, string_type):\n                            self.cfg.update('sources', [{'filename': source, 'source_urls': comp_cfg['source_urls']}])\n                        elif isinstance(source, dict):\n                            # Update source_urls in the 'source' dict to use the one for the components\n                            # (if it doesn't already exist)\n                            if 'source_urls' not in source:\n                                source['source_urls'] = comp_cfg['source_urls']\n                            self.cfg.update('sources', [source])\n                        else:\n                            raise EasyBuildError(\"Source %s for component %s is neither a string nor a dict, cannot \"\n                                                 \"process it.\", source, comp_cfg['name'])\n                else:\n                    # add component sources to list of sources\n                    self.cfg.update('sources', comp_cfg['sources'])\n            else:\n                raise EasyBuildError(\"No sources specification for component %s v%s\", comp_name, comp_version)\n\n            if comp_cfg['checksums']:\n                src_cnt = len(comp_cfg['sources'])\n\n                # add per-component checksums for sources to list of checksums\n                self.cfg.update('checksums', comp_cfg['checksums'][:src_cnt])\n\n                # add per-component checksums for patches to list of checksums for patches\n                checksums_patches.extend(comp_cfg['checksums'][src_cnt:])\n\n            if comp_cfg['patches']:\n                self.cfg.update('patches', comp_cfg['patches'])\n\n            self.comp_cfgs.append(comp_cfg)\n\n        self.cfg.update('checksums', checksums_patches)\n\n        self.cfg.enable_templating = True\n\n    def check_checksums(self):\n        \"\"\"\n        Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).\n\n        :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)\n        \"\"\"\n        checksum_issues = super(Bundle, self).check_checksums()\n\n        for comp in self.comp_cfgs:\n            checksum_issues.extend(self.check_checksums_for(comp, sub=\"of component %s\" % comp['name']))\n\n        return checksum_issues\n\n    def patch_step(self):\n        \"\"\"Patch step must be a no-op for bundle, since there are no top-level sources/patches.\"\"\"\n        pass\n\n    def get_altroot_and_altversion(self):\n        \"\"\"Get altroot and altversion, if they are defined\"\"\"\n        altroot = None\n        if self.cfg['altroot']:\n            altroot = get_software_root(self.cfg['altroot'])\n        altversion = None\n        if self.cfg['altversion']:\n            altversion = get_software_version(self.cfg['altversion'])\n        return altroot, altversion\n\n    def configure_step(self):\n        \"\"\"Collect altroot/altversion info.\"\"\"\n        self.altroot, self.altversion = self.get_altroot_and_altversion()\n\n    def build_step(self):\n        \"\"\"Do nothing.\"\"\"\n        pass\n\n    def install_step(self):\n        \"\"\"Install components, if specified.\"\"\"\n        comp_cnt = len(self.cfg['components'])\n        for idx, cfg in enumerate(self.comp_cfgs):\n\n            print_msg(\"installing bundle component %s v%s (%d/%d)...\" %\n                      (cfg['name'], cfg['version'], idx + 1, comp_cnt))\n            self.log.info(\"Installing component %s v%s using easyblock %s\", cfg['name'], cfg['version'], cfg.easyblock)\n\n            comp = cfg.easyblock(cfg)\n\n            # correct build/install dirs\n            comp.builddir = self.builddir\n            comp.install_subdir, comp.installdir = self.install_subdir, self.installdir\n\n            # make sure we can build in parallel\n            comp.set_parallel()\n\n            # figure out correct start directory\n            comp.guess_start_dir()\n\n            # need to run fetch_patches to ensure per-component patches are applied\n            comp.fetch_patches()\n\n            comp.src = []\n\n            # find match entries in self.src for this component\n            for source in comp.cfg['sources']:\n                if isinstance(source, string_type):\n                    comp_src_fn = source\n                elif isinstance(source, dict):\n                    if 'filename' in source:\n                        comp_src_fn = source['filename']\n                    else:\n                        raise EasyBuildError(\"Encountered source file specified as dict without 'filename': %s\", source)\n                else:\n                    raise EasyBuildError(\"Specification of unknown type for source file: %s\", source)\n\n                found = False\n                for src in self.src:\n                    if src['name'] == comp_src_fn:\n                        self.log.info(\"Found spec for source %s for component %s: %s\", comp_src_fn, comp.name, src)\n                        comp.src.append(src)\n                        found = True\n                        break\n                if not found:\n                    raise EasyBuildError(\"Failed to find spec for source %s for component %s\", comp_src_fn, comp.name)\n\n                # location of first unpacked source is used to determine where to apply patch(es)\n                comp.src[-1]['finalpath'] = comp.cfg['start_dir']\n\n            # run relevant steps\n            for step_name in ['patch', 'configure', 'build', 'install']:\n                if step_name in cfg['skipsteps']:\n                    comp.log.info(\"Skipping '%s' step for component %s v%s\", step_name, cfg['name'], cfg['version'])\n                else:\n                    comp.run_step(step_name, [lambda x: getattr(x, '%s_step' % step_name)])\n\n            # update environment to ensure stuff provided by former components can be picked up by latter components\n            # once the installation is finalised, this is handled by the generated module\n            reqs = comp.make_module_req_guess()\n            for envvar in reqs:\n                curr_val = os.getenv(envvar, '')\n                curr_paths = curr_val.split(os.pathsep)\n                for subdir in reqs[envvar]:\n                    path = os.path.join(self.installdir, subdir)\n                    if path not in curr_paths:\n                        if curr_val:\n                            new_val = '%s:%s' % (path, curr_val)\n                        else:\n                            new_val = path\n                        env.setvar(envvar, new_val)\n\n    def make_module_extra(self, *args, **kwargs):\n        \"\"\"Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc.\"\"\"\n        if not self.altroot and not self.altversion:\n            # check for altroot and altversion (needed here for a module only build)\n            self.altroot, self.altversion = self.get_altroot_and_altversion()\n        if 'altroot' not in kwargs:\n            kwargs['altroot'] = self.altroot\n        if 'altversion' not in kwargs:\n            kwargs['altversion'] = self.altversion\n        return super(Bundle, self).make_module_extra(*args, **kwargs)\n\n    def sanity_check_step(self, *args, **kwargs):\n        \"\"\"\n        Nothing is being installed, so just being able to load the (fake) module is sufficient\n        \"\"\"\n        if self.cfg['exts_list'] or self.cfg['sanity_check_paths'] or self.cfg['sanity_check_commands']:\n            super(Bundle, self).sanity_check_step(*args, **kwargs)\n        else:\n            self.log.info(\"Testing loading of module '%s' by means of sanity check\" % self.full_mod_name)\n            fake_mod_data = self.load_fake_module(purge=True)\n            self.log.debug(\"Cleaning up after testing loading of module\")\n            self.clean_up_fake_module(fake_mod_data)\n", "path": "easybuild/easyblocks/generic/bundle.py"}]}