We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bundle Easyblock does not put patches into root config
At https://github.com/easybuilders/easybuild-easyblocks/blob/b99cc5a4dfb98cafbbd4a8827ea9bfb444724e27/easybuild/easyblocks/generic/bundle.py#L156 the patches checksums are added to the root, but the patches are not which makes e.g. the unit tests fail, see https://github.com/easybuilders/easybuild-easyconfigs/pull/9546

Should the patches be added? From the logic in the check_checksums it seems: yes

--- 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-2019 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 
78         # disable templating to avoid premature resolving of template values
79         self.cfg.enable_templating = False
80 
81         # list of checksums for patches (must be included after checksums for sources)
82         checksums_patches = []
83 
84         for comp in self.cfg['components']:
85             comp_name, comp_version, comp_specs = comp[0], comp[1], {}
86             if len(comp) == 3:
87                 comp_specs = comp[2]
88 
89             comp_cfg = self.cfg.copy()
90 
91             easyblock = comp_specs.get('easyblock') or self.cfg['default_easyblock']
92             if easyblock is None:
93                 raise EasyBuildError("No easyblock specified for component %s v%s", comp_cfg['name'],
94                                      comp_cfg['version'])
95             elif easyblock == 'Bundle':
96                 raise EasyBuildError("The Bundle easyblock can not be used to install components in a bundle")
97 
98             comp_cfg.easyblock = get_easyblock_class(easyblock, name=comp_cfg['name'])
99 
100             # make sure that extra easyconfig parameters are known, so they can be set
101             extra_opts = comp_cfg.easyblock.extra_options()
102             comp_cfg.extend_params(copy.deepcopy(extra_opts))
103 
104             comp_cfg['name'] = comp_name
105             comp_cfg['version'] = comp_version
106             comp_cfg.generate_template_values()
107 
108             # do not inherit easyblock to use from parent (since that would result in an infinite loop in install_step)
109             comp_cfg['easyblock'] = None
110 
111             # reset list of sources/source_urls/checksums
112             comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = []
113 
114             for key in self.cfg['default_component_specs']:
115                 comp_cfg[key] = self.cfg['default_component_specs'][key]
116 
117             for key in comp_specs:
118                 comp_cfg[key] = comp_specs[key]
119 
120             # enable resolving of templates for component-specific EasyConfig instance
121             comp_cfg.enable_templating = True
122 
123             # 'sources' is strictly required
124             if comp_cfg['sources']:
125                 # If per-component source URLs are provided, attach them directly to the relevant sources
126                 if comp_cfg['source_urls']:
127                     for source in comp_cfg['sources']:
128                         if isinstance(source, string_type):
129                             self.cfg.update('sources', [{'filename': source, 'source_urls': comp_cfg['source_urls']}])
130                         elif isinstance(source, dict):
131                             # Update source_urls in the 'source' dict to use the one for the components
132                             # (if it doesn't already exist)
133                             if 'source_urls' not in source:
134                                 source['source_urls'] = comp_cfg['source_urls']
135                             self.cfg.update('sources', [source])
136                         else:
137                             raise EasyBuildError("Source %s for component %s is neither a string nor a dict, cannot "
138                                                  "process it.", source, comp_cfg['name'])
139                 else:
140                     # add component sources to list of sources
141                     self.cfg.update('sources', comp_cfg['sources'])
142             else:
143                 raise EasyBuildError("No sources specification for component %s v%s", comp_name, comp_version)
144 
145             if comp_cfg['checksums']:
146                 src_cnt = len(comp_cfg['sources'])
147 
148                 # add per-component checksums for sources to list of checksums
149                 self.cfg.update('checksums', comp_cfg['checksums'][:src_cnt])
150 
151                 # add per-component checksums for patches to list of checksums for patches
152                 checksums_patches.extend(comp_cfg['checksums'][src_cnt:])
153 
154             self.comp_cfgs.append(comp_cfg)
155 
156         self.cfg.update('checksums', checksums_patches)
157 
158         self.cfg.enable_templating = True
159 
160     def check_checksums(self):
161         """
162         Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
163 
164         :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
165         """
166         checksum_issues = super(Bundle, self).check_checksums()
167 
168         for comp in self.comp_cfgs:
169             checksum_issues.extend(self.check_checksums_for(comp, sub="of component %s" % comp['name']))
170 
171         return checksum_issues
172 
173     def configure_step(self):
174         """Collect altroot/altversion info."""
175         # pick up altroot/altversion, if they are defined
176         self.altroot = None
177         if self.cfg['altroot']:
178             self.altroot = get_software_root(self.cfg['altroot'])
179         self.altversion = None
180         if self.cfg['altversion']:
181             self.altversion = get_software_version(self.cfg['altversion'])
182 
183     def build_step(self):
184         """Do nothing."""
185         pass
186 
187     def install_step(self):
188         """Install components, if specified."""
189         comp_cnt = len(self.cfg['components'])
190         for idx, cfg in enumerate(self.comp_cfgs):
191 
192             print_msg("installing bundle component %s v%s (%d/%d)..." % (cfg['name'], cfg['version'], idx+1, comp_cnt))
193             self.log.info("Installing component %s v%s using easyblock %s", cfg['name'], cfg['version'], cfg.easyblock)
194 
195             comp = cfg.easyblock(cfg)
196 
197             # correct build/install dirs
198             comp.builddir = self.builddir
199             comp.install_subdir, comp.installdir = self.install_subdir, self.installdir
200 
201             # make sure we can build in parallel
202             comp.set_parallel()
203 
204             # figure out correct start directory
205             comp.guess_start_dir()
206 
207             # need to run fetch_patches to ensure per-component patches are applied
208             comp.fetch_patches()
209 
210             comp.src = []
211 
212             # find match entries in self.src for this component
213             for source in comp.cfg['sources']:
214                 if isinstance(source, string_type):
215                     comp_src_fn = source
216                 elif isinstance(source, dict):
217                     if 'filename' in source:
218                         comp_src_fn = source['filename']
219                     else:
220                         raise EasyBuildError("Encountered source file specified as dict without 'filename': %s", source)
221                 else:
222                     raise EasyBuildError("Specification of unknown type for source file: %s", source)
223 
224                 found = False
225                 for src in self.src:
226                     if src['name'] == comp_src_fn:
227                         self.log.info("Found spec for source %s for component %s: %s", comp_src_fn, comp.name, src)
228                         comp.src.append(src)
229                         found = True
230                         break
231                 if not found:
232                     raise EasyBuildError("Failed to find spec for source %s for component %s", comp_src_fn, comp.name)
233 
234                 # location of first unpacked source is used to determine where to apply patch(es)
235                 comp.src[-1]['finalpath'] = comp.cfg['start_dir']
236 
237             # run relevant steps
238             for step_name in ['patch', 'configure', 'build', 'install']:
239                 if step_name in cfg['skipsteps']:
240                     comp.log.info("Skipping '%s' step for component %s v%s", step_name, cfg['name'], cfg['version'])
241                 else:
242                     comp.run_step(step_name, [lambda x: getattr(x, '%s_step' % step_name)])
243 
244             # update environment to ensure stuff provided by former components can be picked up by latter components
245             # once the installation is finalised, this is handled by the generated module
246             reqs = comp.make_module_req_guess()
247             for envvar in reqs:
248                 curr_val = os.getenv(envvar, '')
249                 curr_paths = curr_val.split(os.pathsep)
250                 for subdir in reqs[envvar]:
251                     path = os.path.join(self.installdir, subdir)
252                     if path not in curr_paths:
253                         if curr_val:
254                             new_val = '%s:%s' % (path, curr_val)
255                         else:
256                             new_val = path
257                         env.setvar(envvar, new_val)
258 
259     def make_module_extra(self, *args, **kwargs):
260         """Set extra stuff in module file, e.g. $EBROOT*, $EBVERSION*, etc."""
261         if 'altroot' not in kwargs:
262             kwargs['altroot'] = self.altroot
263         if 'altversion' not in kwargs:
264             kwargs['altversion'] = self.altversion
265         return super(Bundle, self).make_module_extra(*args, **kwargs)
266 
267     def sanity_check_step(self, *args, **kwargs):
268         """
269         Nothing is being installed, so just being able to load the (fake) module is sufficient
270         """
271         if self.cfg['exts_list'] or self.cfg['sanity_check_paths'] or self.cfg['sanity_check_commands']:
272             super(Bundle, self).sanity_check_step(*args, **kwargs)
273         else:
274             self.log.info("Testing loading of module '%s' by means of sanity check" % self.full_mod_name)
275             fake_mod_data = self.load_fake_module(purge=True)
276             self.log.debug("Cleaning up after testing loading of module")
277             self.clean_up_fake_module(fake_mod_data)
278
```
--- 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
@@ -74,6 +74,8 @@
         # list of sources for bundle itself *must* be empty
         if self.cfg['sources']:
             raise EasyBuildError("List of sources for bundle itself must be empty, found %s", self.cfg['sources'])
+        if self.cfg['patches']:
+            raise EasyBuildError("List of patches for bundle itself must be empty, found %s", self.cfg['patches'])
 
         # disable templating to avoid premature resolving of template values
         self.cfg.enable_templating = False
@@ -109,7 +111,7 @@
             comp_cfg['easyblock'] = None
 
             # reset list of sources/source_urls/checksums
-            comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = []
+            comp_cfg['sources'] = comp_cfg['source_urls'] = comp_cfg['checksums'] = comp_cfg['patches'] = []
 
             for key in self.cfg['default_component_specs']:
                 comp_cfg[key] = self.cfg['default_component_specs'][key]
@@ -151,6 +153,9 @@
                 # add per-component checksums for patches to list of checksums for patches
                 checksums_patches.extend(comp_cfg['checksums'][src_cnt:])
 
+            if comp_cfg['patches']:
+                self.cfg.update('patches', comp_cfg['patches'])
+
             self.comp_cfgs.append(comp_cfg)
 
         self.cfg.update('checksums', checksums_patches)

{"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@@ -74,6 +74,8 @@\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@@ -109,7 +111,7 @@\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'] = []\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@@ -151,6 +153,9 @@\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", "issue": "Bundle Easyblock does not put patches into root config\nAt https://github.com/easybuilders/easybuild-easyblocks/blob/b99cc5a4dfb98cafbbd4a8827ea9bfb444724e27/easybuild/easyblocks/generic/bundle.py#L156 the patches checksums are added to the root, but the patches are not which makes e.g. the unit tests fail, see https://github.com/easybuilders/easybuild-easyconfigs/pull/9546\r\n\r\nShould the patches be added? From the logic in the check_checksums it seems: yes\n", "before_files": [{"content": "##\n# Copyright 2009-2019 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\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            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['name'] = comp_name\n            comp_cfg['version'] = comp_version\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'] = []\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            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 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)...\" % (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-2019 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            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['name'] = comp_name\n            comp_cfg['version'] = comp_version\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 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)...\" % (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"}]}