We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Endless job submission when job=True in config file
An endless number of jobs are submitted via Slurm when the following option is enabled in the `.cfg` file:

```
# Submit the build as a job (default: False)
job=True
```
No build process is actually started; it seems each job just spawns another Slurm job that is then added to the queue.

For example, issuing `eb -r zlib-1.2.8.eb` will yield the following sample output from any Slurm job:

```
== temporary log file in case of crash ~/easybuild/tmp/eb-viHTks/easybuild-2KFvjK.log
== List of submitted jobs (1): zlib-1.2.8 (Core/zlib/1.2.8): 632849
== Submitted parallel build jobs, exiting now
== Temporary log file(s) ~/easybuild/tmp/eb-viHTks/easybuild-2KFvjK.log* have been removed.
== Temporary directory ~/easybuild/tmp/eb-viHTks has been removed.
```

--- 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/tools/parallelbuild.py`
Content:
```
1 # #
2 # Copyright 2012-2020 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 Module for doing parallel builds. This uses a PBS-like cluster. You should be able to submit jobs (which can have
27 dependencies)
28 
29 Support for PBS is provided via the PbsJob class. If you want you could create other job classes and use them here.
30 
31 :author: Toon Willems (Ghent University)
32 :author: Kenneth Hoste (Ghent University)
33 :author: Stijn De Weirdt (Ghent University)
34 """
35 import math
36 import os
37 import re
38 
39 from easybuild.base import fancylogger
40 from easybuild.framework.easyblock import get_easyblock_instance
41 from easybuild.framework.easyconfig.easyconfig import ActiveMNS
42 from easybuild.tools.build_log import EasyBuildError
43 from easybuild.tools.config import build_option, get_repository, get_repositorypath
44 from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
45 from easybuild.tools.job.backend import job_backend
46 from easybuild.tools.repository.repository import init_repository
47 
48 
49 _log = fancylogger.getLogger('parallelbuild', fname=False)
50 
51 
52 def _to_key(dep):
53     """Determine key for specified dependency."""
54     return ActiveMNS().det_full_module_name(dep)
55 
56 
57 def build_easyconfigs_in_parallel(build_command, easyconfigs, output_dir='easybuild-build', prepare_first=True):
58     """
59     Build easyconfigs in parallel by submitting jobs to a batch-queuing system.
60     Return list of jobs submitted.
61 
62     Argument `easyconfigs` is a list of easyconfigs which can be
63     built: e.g. they have no unresolved dependencies.  This function
64     will build them in parallel by submitting jobs.
65 
66     :param build_command: build command to use
67     :param easyconfigs: list of easyconfig files
68     :param output_dir: output directory
69     :param prepare_first: prepare by runnning fetch step first for each easyconfig
70     """
71     _log.info("going to build these easyconfigs in parallel: %s", [os.path.basename(ec['spec']) for ec in easyconfigs])
72 
73     active_job_backend = job_backend()
74     if active_job_backend is None:
75         raise EasyBuildError("Can not use --job if no job backend is available.")
76 
77     try:
78         active_job_backend.init()
79     except RuntimeError as err:
80         raise EasyBuildError("connection to server failed (%s: %s), can't submit jobs.", err.__class__.__name__, err)
81 
82     # dependencies have already been resolved,
83     # so one can linearly walk over the list and use previous job id's
84     jobs = []
85 
86     # keep track of which job builds which module
87     module_to_job = {}
88 
89     for easyconfig in easyconfigs:
90         # this is very important, otherwise we might have race conditions
91         # e.g. GCC-4.5.3 finds cloog.tar.gz but it was incorrectly downloaded by GCC-4.6.3
92         # running this step here, prevents this
93         if prepare_first:
94             prepare_easyconfig(easyconfig)
95 
96         # the new job will only depend on already submitted jobs
97         _log.info("creating job for ec: %s" % os.path.basename(easyconfig['spec']))
98         new_job = create_job(active_job_backend, build_command, easyconfig, output_dir=output_dir)
99 
100         # filter out dependencies marked as external modules
101         deps = [d for d in easyconfig['ec'].all_dependencies if not d.get('external_module', False)]
102 
103         dep_mod_names = map(ActiveMNS().det_full_module_name, deps)
104         job_deps = [module_to_job[dep] for dep in dep_mod_names if dep in module_to_job]
105 
106         # actually (try to) submit job
107         active_job_backend.queue(new_job, job_deps)
108         _log.info("job %s for module %s has been submitted", new_job, new_job.module)
109 
110         # update dictionary
111         module_to_job[new_job.module] = new_job
112         jobs.append(new_job)
113 
114     active_job_backend.complete()
115 
116     return jobs
117 
118 
119 def submit_jobs(ordered_ecs, cmd_line_opts, testing=False, prepare_first=True):
120     """
121     Submit jobs.
122     :param ordered_ecs: list of easyconfigs, in the order they should be processed
123     :param cmd_line_opts: list of command line options (in 'longopt=value' form)
124     :param testing: If `True`, skip actual job submission
125     :param prepare_first: prepare by runnning fetch step first for each easyconfig
126     """
127     curdir = os.getcwd()
128 
129     # regex pattern for options to ignore (help options can't reach here)
130     ignore_opts = re.compile('^--robot$|^--job$|^--try-.*$')
131 
132     # generate_cmd_line returns the options in form --longopt=value
133     opts = [o for o in cmd_line_opts if not ignore_opts.match(o.split('=')[0])]
134 
135     # compose string with command line options, properly quoted and with '%' characters escaped
136     opts_str = ' '.join(opts).replace('%', '%%')
137 
138     command = "unset TMPDIR && cd %s && eb %%(spec)s %s %%(add_opts)s --testoutput=%%(output_dir)s" % (curdir, opts_str)
139     _log.info("Command template for jobs: %s" % command)
140     if testing:
141         _log.debug("Skipping actual submission of jobs since testing mode is enabled")
142         return command
143     else:
144         return build_easyconfigs_in_parallel(command, ordered_ecs, prepare_first=prepare_first)
145 
146 
147 def create_job(job_backend, build_command, easyconfig, output_dir='easybuild-build'):
148     """
149     Creates a job to build a *single* easyconfig.
150 
151     :param job_backend: A factory object for querying server parameters and creating actual job objects
152     :param build_command: format string for command, full path to an easyconfig file will be substituted in it
153     :param easyconfig: easyconfig as processed by process_easyconfig
154     :param output_dir: optional output path; --regtest-output-dir will be used inside the job with this variable
155 
156     returns the job
157     """
158     # obtain unique name based on name/easyconfig version tuple
159     ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec']))
160     name = '-'.join(ec_tuple)
161 
162     # determine whether additional options need to be passed to the 'eb' command
163     add_opts = ''
164     if easyconfig['hidden']:
165         add_opts += ' --hidden'
166 
167     # create command based on build_command template
168     command = build_command % {
169         'add_opts': add_opts,
170         'output_dir': os.path.join(os.path.abspath(output_dir), name),
171         'spec': easyconfig['spec'],
172     }
173 
174     # just use latest build stats
175     repo = init_repository(get_repository(), get_repositorypath())
176     buildstats = repo.get_buildstats(*ec_tuple)
177     extra = {}
178     if buildstats:
179         previous_time = buildstats[-1]['build_time']
180         extra['hours'] = int(math.ceil(previous_time * 2 / 60))
181 
182     if build_option('job_cores'):
183         extra['cores'] = build_option('job_cores')
184 
185     job = job_backend.make_job(command, name, **extra)
186     job.module = easyconfig['ec'].full_mod_name
187 
188     return job
189 
190 
191 def prepare_easyconfig(ec):
192     """
193     Prepare for building specified easyconfig (fetch sources)
194     :param ec: parsed easyconfig (EasyConfig instance)
195     """
196     try:
197         easyblock_instance = get_easyblock_instance(ec)
198         easyblock_instance.update_config_template_run_step()
199         easyblock_instance.fetch_step(skip_checksums=True)
200         _log.debug("Cleaning up log file %s..." % easyblock_instance.logfile)
201         easyblock_instance.close_log()
202         os.remove(easyblock_instance.logfile)
203     except (OSError, EasyBuildError) as err:
204         raise EasyBuildError("An error occurred while preparing %s: %s", ec, err)
205
```
--- 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/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py
--- a/easybuild/tools/parallelbuild.py
+++ b/easybuild/tools/parallelbuild.py
@@ -127,11 +127,17 @@
     curdir = os.getcwd()
 
     # regex pattern for options to ignore (help options can't reach here)
-    ignore_opts = re.compile('^--robot$|^--job$|^--try-.*$')
+    ignore_opts = re.compile('^--robot$|^--job|^--try-.*$')
 
     # generate_cmd_line returns the options in form --longopt=value
     opts = [o for o in cmd_line_opts if not ignore_opts.match(o.split('=')[0])]
 
+    # add --disable-job to make sure the submitted job doesn't submit a job itself,
+    # resulting in an infinite cycle of jobs;
+    # this can happen if job submission is enabled via a configuration file or via $EASYBUILD_JOB,
+    # cfr. https://github.com/easybuilders/easybuild-framework/issues/3307
+    opts.append('--disable-job')
+
     # compose string with command line options, properly quoted and with '%' characters escaped
     opts_str = ' '.join(opts).replace('%', '%%')

{"golden_diff": "diff --git a/easybuild/tools/parallelbuild.py b/easybuild/tools/parallelbuild.py\n--- a/easybuild/tools/parallelbuild.py\n+++ b/easybuild/tools/parallelbuild.py\n@@ -127,11 +127,17 @@\n     curdir = os.getcwd()\n \n     # regex pattern for options to ignore (help options can't reach here)\n-    ignore_opts = re.compile('^--robot$|^--job$|^--try-.*$')\n+    ignore_opts = re.compile('^--robot$|^--job|^--try-.*$')\n \n     # generate_cmd_line returns the options in form --longopt=value\n     opts = [o for o in cmd_line_opts if not ignore_opts.match(o.split('=')[0])]\n \n+    # add --disable-job to make sure the submitted job doesn't submit a job itself,\n+    # resulting in an infinite cycle of jobs;\n+    # this can happen if job submission is enabled via a configuration file or via $EASYBUILD_JOB,\n+    # cfr. https://github.com/easybuilders/easybuild-framework/issues/3307\n+    opts.append('--disable-job')\n+\n     # compose string with command line options, properly quoted and with '%' characters escaped\n     opts_str = ' '.join(opts).replace('%', '%%')\n", "issue": "Endless job submission when job=True in config file\nAn endless number of jobs are submitted via Slurm when the following option is enabled in the `.cfg` file:\r\n\r\n```\r\n# Submit the build as a job (default: False)\r\njob=True\r\n```\r\nNo build process is actually started; it seems each job just spawns another Slurm job that is then added to the queue.\r\n\r\nFor example, issuing `eb -r zlib-1.2.8.eb` will yield the following sample output from any Slurm job:\r\n\r\n```\r\n== temporary log file in case of crash ~/easybuild/tmp/eb-viHTks/easybuild-2KFvjK.log\r\n== List of submitted jobs (1): zlib-1.2.8 (Core/zlib/1.2.8): 632849\r\n== Submitted parallel build jobs, exiting now\r\n== Temporary log file(s) ~/easybuild/tmp/eb-viHTks/easybuild-2KFvjK.log* have been removed.\r\n== Temporary directory ~/easybuild/tmp/eb-viHTks has been removed.\r\n```\n", "before_files": [{"content": "# #\n# Copyright 2012-2020 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\"\"\"\nModule for doing parallel builds. This uses a PBS-like cluster. You should be able to submit jobs (which can have\ndependencies)\n\nSupport for PBS is provided via the PbsJob class. If you want you could create other job classes and use them here.\n\n:author: Toon Willems (Ghent University)\n:author: Kenneth Hoste (Ghent University)\n:author: Stijn De Weirdt (Ghent University)\n\"\"\"\nimport math\nimport os\nimport re\n\nfrom easybuild.base import fancylogger\nfrom easybuild.framework.easyblock import get_easyblock_instance\nfrom easybuild.framework.easyconfig.easyconfig import ActiveMNS\nfrom easybuild.tools.build_log import EasyBuildError\nfrom easybuild.tools.config import build_option, get_repository, get_repositorypath\nfrom easybuild.tools.module_naming_scheme.utilities import det_full_ec_version\nfrom easybuild.tools.job.backend import job_backend\nfrom easybuild.tools.repository.repository import init_repository\n\n\n_log = fancylogger.getLogger('parallelbuild', fname=False)\n\n\ndef _to_key(dep):\n    \"\"\"Determine key for specified dependency.\"\"\"\n    return ActiveMNS().det_full_module_name(dep)\n\n\ndef build_easyconfigs_in_parallel(build_command, easyconfigs, output_dir='easybuild-build', prepare_first=True):\n    \"\"\"\n    Build easyconfigs in parallel by submitting jobs to a batch-queuing system.\n    Return list of jobs submitted.\n\n    Argument `easyconfigs` is a list of easyconfigs which can be\n    built: e.g. they have no unresolved dependencies.  This function\n    will build them in parallel by submitting jobs.\n\n    :param build_command: build command to use\n    :param easyconfigs: list of easyconfig files\n    :param output_dir: output directory\n    :param prepare_first: prepare by runnning fetch step first for each easyconfig\n    \"\"\"\n    _log.info(\"going to build these easyconfigs in parallel: %s\", [os.path.basename(ec['spec']) for ec in easyconfigs])\n\n    active_job_backend = job_backend()\n    if active_job_backend is None:\n        raise EasyBuildError(\"Can not use --job if no job backend is available.\")\n\n    try:\n        active_job_backend.init()\n    except RuntimeError as err:\n        raise EasyBuildError(\"connection to server failed (%s: %s), can't submit jobs.\", err.__class__.__name__, err)\n\n    # dependencies have already been resolved,\n    # so one can linearly walk over the list and use previous job id's\n    jobs = []\n\n    # keep track of which job builds which module\n    module_to_job = {}\n\n    for easyconfig in easyconfigs:\n        # this is very important, otherwise we might have race conditions\n        # e.g. GCC-4.5.3 finds cloog.tar.gz but it was incorrectly downloaded by GCC-4.6.3\n        # running this step here, prevents this\n        if prepare_first:\n            prepare_easyconfig(easyconfig)\n\n        # the new job will only depend on already submitted jobs\n        _log.info(\"creating job for ec: %s\" % os.path.basename(easyconfig['spec']))\n        new_job = create_job(active_job_backend, build_command, easyconfig, output_dir=output_dir)\n\n        # filter out dependencies marked as external modules\n        deps = [d for d in easyconfig['ec'].all_dependencies if not d.get('external_module', False)]\n\n        dep_mod_names = map(ActiveMNS().det_full_module_name, deps)\n        job_deps = [module_to_job[dep] for dep in dep_mod_names if dep in module_to_job]\n\n        # actually (try to) submit job\n        active_job_backend.queue(new_job, job_deps)\n        _log.info(\"job %s for module %s has been submitted\", new_job, new_job.module)\n\n        # update dictionary\n        module_to_job[new_job.module] = new_job\n        jobs.append(new_job)\n\n    active_job_backend.complete()\n\n    return jobs\n\n\ndef submit_jobs(ordered_ecs, cmd_line_opts, testing=False, prepare_first=True):\n    \"\"\"\n    Submit jobs.\n    :param ordered_ecs: list of easyconfigs, in the order they should be processed\n    :param cmd_line_opts: list of command line options (in 'longopt=value' form)\n    :param testing: If `True`, skip actual job submission\n    :param prepare_first: prepare by runnning fetch step first for each easyconfig\n    \"\"\"\n    curdir = os.getcwd()\n\n    # regex pattern for options to ignore (help options can't reach here)\n    ignore_opts = re.compile('^--robot$|^--job$|^--try-.*$')\n\n    # generate_cmd_line returns the options in form --longopt=value\n    opts = [o for o in cmd_line_opts if not ignore_opts.match(o.split('=')[0])]\n\n    # compose string with command line options, properly quoted and with '%' characters escaped\n    opts_str = ' '.join(opts).replace('%', '%%')\n\n    command = \"unset TMPDIR && cd %s && eb %%(spec)s %s %%(add_opts)s --testoutput=%%(output_dir)s\" % (curdir, opts_str)\n    _log.info(\"Command template for jobs: %s\" % command)\n    if testing:\n        _log.debug(\"Skipping actual submission of jobs since testing mode is enabled\")\n        return command\n    else:\n        return build_easyconfigs_in_parallel(command, ordered_ecs, prepare_first=prepare_first)\n\n\ndef create_job(job_backend, build_command, easyconfig, output_dir='easybuild-build'):\n    \"\"\"\n    Creates a job to build a *single* easyconfig.\n\n    :param job_backend: A factory object for querying server parameters and creating actual job objects\n    :param build_command: format string for command, full path to an easyconfig file will be substituted in it\n    :param easyconfig: easyconfig as processed by process_easyconfig\n    :param output_dir: optional output path; --regtest-output-dir will be used inside the job with this variable\n\n    returns the job\n    \"\"\"\n    # obtain unique name based on name/easyconfig version tuple\n    ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec']))\n    name = '-'.join(ec_tuple)\n\n    # determine whether additional options need to be passed to the 'eb' command\n    add_opts = ''\n    if easyconfig['hidden']:\n        add_opts += ' --hidden'\n\n    # create command based on build_command template\n    command = build_command % {\n        'add_opts': add_opts,\n        'output_dir': os.path.join(os.path.abspath(output_dir), name),\n        'spec': easyconfig['spec'],\n    }\n\n    # just use latest build stats\n    repo = init_repository(get_repository(), get_repositorypath())\n    buildstats = repo.get_buildstats(*ec_tuple)\n    extra = {}\n    if buildstats:\n        previous_time = buildstats[-1]['build_time']\n        extra['hours'] = int(math.ceil(previous_time * 2 / 60))\n\n    if build_option('job_cores'):\n        extra['cores'] = build_option('job_cores')\n\n    job = job_backend.make_job(command, name, **extra)\n    job.module = easyconfig['ec'].full_mod_name\n\n    return job\n\n\ndef prepare_easyconfig(ec):\n    \"\"\"\n    Prepare for building specified easyconfig (fetch sources)\n    :param ec: parsed easyconfig (EasyConfig instance)\n    \"\"\"\n    try:\n        easyblock_instance = get_easyblock_instance(ec)\n        easyblock_instance.update_config_template_run_step()\n        easyblock_instance.fetch_step(skip_checksums=True)\n        _log.debug(\"Cleaning up log file %s...\" % easyblock_instance.logfile)\n        easyblock_instance.close_log()\n        os.remove(easyblock_instance.logfile)\n    except (OSError, EasyBuildError) as err:\n        raise EasyBuildError(\"An error occurred while preparing %s: %s\", ec, err)\n", "path": "easybuild/tools/parallelbuild.py"}], "after_files": [{"content": "# #\n# Copyright 2012-2020 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\"\"\"\nModule for doing parallel builds. This uses a PBS-like cluster. You should be able to submit jobs (which can have\ndependencies)\n\nSupport for PBS is provided via the PbsJob class. If you want you could create other job classes and use them here.\n\n:author: Toon Willems (Ghent University)\n:author: Kenneth Hoste (Ghent University)\n:author: Stijn De Weirdt (Ghent University)\n\"\"\"\nimport math\nimport os\nimport re\n\nfrom easybuild.base import fancylogger\nfrom easybuild.framework.easyblock import get_easyblock_instance\nfrom easybuild.framework.easyconfig.easyconfig import ActiveMNS\nfrom easybuild.tools.build_log import EasyBuildError\nfrom easybuild.tools.config import build_option, get_repository, get_repositorypath\nfrom easybuild.tools.module_naming_scheme.utilities import det_full_ec_version\nfrom easybuild.tools.job.backend import job_backend\nfrom easybuild.tools.repository.repository import init_repository\n\n\n_log = fancylogger.getLogger('parallelbuild', fname=False)\n\n\ndef _to_key(dep):\n    \"\"\"Determine key for specified dependency.\"\"\"\n    return ActiveMNS().det_full_module_name(dep)\n\n\ndef build_easyconfigs_in_parallel(build_command, easyconfigs, output_dir='easybuild-build', prepare_first=True):\n    \"\"\"\n    Build easyconfigs in parallel by submitting jobs to a batch-queuing system.\n    Return list of jobs submitted.\n\n    Argument `easyconfigs` is a list of easyconfigs which can be\n    built: e.g. they have no unresolved dependencies.  This function\n    will build them in parallel by submitting jobs.\n\n    :param build_command: build command to use\n    :param easyconfigs: list of easyconfig files\n    :param output_dir: output directory\n    :param prepare_first: prepare by runnning fetch step first for each easyconfig\n    \"\"\"\n    _log.info(\"going to build these easyconfigs in parallel: %s\", [os.path.basename(ec['spec']) for ec in easyconfigs])\n\n    active_job_backend = job_backend()\n    if active_job_backend is None:\n        raise EasyBuildError(\"Can not use --job if no job backend is available.\")\n\n    try:\n        active_job_backend.init()\n    except RuntimeError as err:\n        raise EasyBuildError(\"connection to server failed (%s: %s), can't submit jobs.\", err.__class__.__name__, err)\n\n    # dependencies have already been resolved,\n    # so one can linearly walk over the list and use previous job id's\n    jobs = []\n\n    # keep track of which job builds which module\n    module_to_job = {}\n\n    for easyconfig in easyconfigs:\n        # this is very important, otherwise we might have race conditions\n        # e.g. GCC-4.5.3 finds cloog.tar.gz but it was incorrectly downloaded by GCC-4.6.3\n        # running this step here, prevents this\n        if prepare_first:\n            prepare_easyconfig(easyconfig)\n\n        # the new job will only depend on already submitted jobs\n        _log.info(\"creating job for ec: %s\" % os.path.basename(easyconfig['spec']))\n        new_job = create_job(active_job_backend, build_command, easyconfig, output_dir=output_dir)\n\n        # filter out dependencies marked as external modules\n        deps = [d for d in easyconfig['ec'].all_dependencies if not d.get('external_module', False)]\n\n        dep_mod_names = map(ActiveMNS().det_full_module_name, deps)\n        job_deps = [module_to_job[dep] for dep in dep_mod_names if dep in module_to_job]\n\n        # actually (try to) submit job\n        active_job_backend.queue(new_job, job_deps)\n        _log.info(\"job %s for module %s has been submitted\", new_job, new_job.module)\n\n        # update dictionary\n        module_to_job[new_job.module] = new_job\n        jobs.append(new_job)\n\n    active_job_backend.complete()\n\n    return jobs\n\n\ndef submit_jobs(ordered_ecs, cmd_line_opts, testing=False, prepare_first=True):\n    \"\"\"\n    Submit jobs.\n    :param ordered_ecs: list of easyconfigs, in the order they should be processed\n    :param cmd_line_opts: list of command line options (in 'longopt=value' form)\n    :param testing: If `True`, skip actual job submission\n    :param prepare_first: prepare by runnning fetch step first for each easyconfig\n    \"\"\"\n    curdir = os.getcwd()\n\n    # regex pattern for options to ignore (help options can't reach here)\n    ignore_opts = re.compile('^--robot$|^--job|^--try-.*$')\n\n    # generate_cmd_line returns the options in form --longopt=value\n    opts = [o for o in cmd_line_opts if not ignore_opts.match(o.split('=')[0])]\n\n    # add --disable-job to make sure the submitted job doesn't submit a job itself,\n    # resulting in an infinite cycle of jobs;\n    # this can happen if job submission is enabled via a configuration file or via $EASYBUILD_JOB,\n    # cfr. https://github.com/easybuilders/easybuild-framework/issues/3307\n    opts.append('--disable-job')\n\n    # compose string with command line options, properly quoted and with '%' characters escaped\n    opts_str = ' '.join(opts).replace('%', '%%')\n\n    command = \"unset TMPDIR && cd %s && eb %%(spec)s %s %%(add_opts)s --testoutput=%%(output_dir)s\" % (curdir, opts_str)\n    _log.info(\"Command template for jobs: %s\" % command)\n    if testing:\n        _log.debug(\"Skipping actual submission of jobs since testing mode is enabled\")\n        return command\n    else:\n        return build_easyconfigs_in_parallel(command, ordered_ecs, prepare_first=prepare_first)\n\n\ndef create_job(job_backend, build_command, easyconfig, output_dir='easybuild-build'):\n    \"\"\"\n    Creates a job to build a *single* easyconfig.\n\n    :param job_backend: A factory object for querying server parameters and creating actual job objects\n    :param build_command: format string for command, full path to an easyconfig file will be substituted in it\n    :param easyconfig: easyconfig as processed by process_easyconfig\n    :param output_dir: optional output path; --regtest-output-dir will be used inside the job with this variable\n\n    returns the job\n    \"\"\"\n    # obtain unique name based on name/easyconfig version tuple\n    ec_tuple = (easyconfig['ec']['name'], det_full_ec_version(easyconfig['ec']))\n    name = '-'.join(ec_tuple)\n\n    # determine whether additional options need to be passed to the 'eb' command\n    add_opts = ''\n    if easyconfig['hidden']:\n        add_opts += ' --hidden'\n\n    # create command based on build_command template\n    command = build_command % {\n        'add_opts': add_opts,\n        'output_dir': os.path.join(os.path.abspath(output_dir), name),\n        'spec': easyconfig['spec'],\n    }\n\n    # just use latest build stats\n    repo = init_repository(get_repository(), get_repositorypath())\n    buildstats = repo.get_buildstats(*ec_tuple)\n    extra = {}\n    if buildstats:\n        previous_time = buildstats[-1]['build_time']\n        extra['hours'] = int(math.ceil(previous_time * 2 / 60))\n\n    if build_option('job_cores'):\n        extra['cores'] = build_option('job_cores')\n\n    job = job_backend.make_job(command, name, **extra)\n    job.module = easyconfig['ec'].full_mod_name\n\n    return job\n\n\ndef prepare_easyconfig(ec):\n    \"\"\"\n    Prepare for building specified easyconfig (fetch sources)\n    :param ec: parsed easyconfig (EasyConfig instance)\n    \"\"\"\n    try:\n        easyblock_instance = get_easyblock_instance(ec)\n        easyblock_instance.update_config_template_run_step()\n        easyblock_instance.fetch_step(skip_checksums=True)\n        _log.debug(\"Cleaning up log file %s...\" % easyblock_instance.logfile)\n        easyblock_instance.close_log()\n        os.remove(easyblock_instance.logfile)\n    except (OSError, EasyBuildError) as err:\n        raise EasyBuildError(\"An error occurred while preparing %s: %s\", ec, err)\n", "path": "easybuild/tools/parallelbuild.py"}]}