Added support for --fail-fast install option to terminate on first failure
This commit is contained in:
parent
f54a8a77b4
commit
96932d65a8
@ -4167,16 +4167,23 @@ want to clean up the temporary directory, or if the package isn't
|
|||||||
downloading properly, you might want to run *only* the ``fetch`` stage
|
downloading properly, you might want to run *only* the ``fetch`` stage
|
||||||
of the build.
|
of the build.
|
||||||
|
|
||||||
|
Spack performs best-effort installation of package dependencies by default,
|
||||||
|
which means it will continue to install as many dependencies as possible
|
||||||
|
after detecting failures. If you are trying to install a package with a
|
||||||
|
lot of dependencies where one or more may fail to build, you might want to
|
||||||
|
try the ``--fail-fast`` option to stop the installation process on the first
|
||||||
|
failure.
|
||||||
|
|
||||||
A typical package workflow might look like this:
|
A typical package workflow might look like this:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ spack edit mypackage
|
$ spack edit mypackage
|
||||||
$ spack install mypackage
|
$ spack install --fail-fast mypackage
|
||||||
... build breaks! ...
|
... build breaks! ...
|
||||||
$ spack clean mypackage
|
$ spack clean mypackage
|
||||||
$ spack edit mypackage
|
$ spack edit mypackage
|
||||||
$ spack install mypackage
|
$ spack install --fail-fast mypackage
|
||||||
... repeat clean/install until install works ...
|
... repeat clean/install until install works ...
|
||||||
|
|
||||||
Below are some commands that will allow you some finer-grained
|
Below are some commands that will allow you some finer-grained
|
||||||
|
@ -32,6 +32,7 @@ def update_kwargs_from_args(args, kwargs):
|
|||||||
that will be passed to Package.do_install API"""
|
that will be passed to Package.do_install API"""
|
||||||
|
|
||||||
kwargs.update({
|
kwargs.update({
|
||||||
|
'fail_fast': args.fail_fast,
|
||||||
'keep_prefix': args.keep_prefix,
|
'keep_prefix': args.keep_prefix,
|
||||||
'keep_stage': args.keep_stage,
|
'keep_stage': args.keep_stage,
|
||||||
'restage': not args.dont_restage,
|
'restage': not args.dont_restage,
|
||||||
@ -78,6 +79,9 @@ def setup_parser(subparser):
|
|||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'--overwrite', action='store_true',
|
'--overwrite', action='store_true',
|
||||||
help="reinstall an existing spec, even if it has dependents")
|
help="reinstall an existing spec, even if it has dependents")
|
||||||
|
subparser.add_argument(
|
||||||
|
'--fail-fast', action='store_true',
|
||||||
|
help="stop all builds if any build fails (default is best effort)")
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'--keep-prefix', action='store_true',
|
'--keep-prefix', action='store_true',
|
||||||
help="don't remove the install prefix if installation fails")
|
help="don't remove the install prefix if installation fails")
|
||||||
|
@ -549,6 +549,9 @@ def package_id(pkg):
|
|||||||
dirty (bool): Don't clean the build environment before installing.
|
dirty (bool): Don't clean the build environment before installing.
|
||||||
explicit (bool): True if package was explicitly installed, False
|
explicit (bool): True if package was explicitly installed, False
|
||||||
if package was implicitly installed (as a dependency).
|
if package was implicitly installed (as a dependency).
|
||||||
|
fail_fast (bool): Fail if any dependency fails to install;
|
||||||
|
otherwise, the default is to install as many dependencies as
|
||||||
|
possible (i.e., best effort installation).
|
||||||
fake (bool): Don't really build; install fake stub files instead.
|
fake (bool): Don't really build; install fake stub files instead.
|
||||||
force (bool): Install again, even if already installed.
|
force (bool): Install again, even if already installed.
|
||||||
install_deps (bool): Install dependencies before installing this
|
install_deps (bool): Install dependencies before installing this
|
||||||
@ -1385,11 +1388,14 @@ def install(self, **kwargs):
|
|||||||
|
|
||||||
Args:"""
|
Args:"""
|
||||||
|
|
||||||
|
fail_fast = kwargs.get('fail_fast', False)
|
||||||
install_deps = kwargs.get('install_deps', True)
|
install_deps = kwargs.get('install_deps', True)
|
||||||
keep_prefix = kwargs.get('keep_prefix', False)
|
keep_prefix = kwargs.get('keep_prefix', False)
|
||||||
keep_stage = kwargs.get('keep_stage', False)
|
keep_stage = kwargs.get('keep_stage', False)
|
||||||
restage = kwargs.get('restage', False)
|
restage = kwargs.get('restage', False)
|
||||||
|
|
||||||
|
fail_fast_err = 'Terminating after first install failure'
|
||||||
|
|
||||||
# install_package defaults True and is popped so that dependencies are
|
# install_package defaults True and is popped so that dependencies are
|
||||||
# always installed regardless of whether the root was installed
|
# always installed regardless of whether the root was installed
|
||||||
install_package = kwargs.pop('install_package', True)
|
install_package = kwargs.pop('install_package', True)
|
||||||
@ -1449,6 +1455,10 @@ def install(self, **kwargs):
|
|||||||
if pkg_id in self.failed or spack.store.db.prefix_failed(spec):
|
if pkg_id in self.failed or spack.store.db.prefix_failed(spec):
|
||||||
tty.warn('{0} failed to install'.format(pkg_id))
|
tty.warn('{0} failed to install'.format(pkg_id))
|
||||||
self._update_failed(task)
|
self._update_failed(task)
|
||||||
|
|
||||||
|
if fail_fast:
|
||||||
|
raise InstallError(fail_fast_err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Attempt to get a write lock. If we can't get the lock then
|
# Attempt to get a write lock. If we can't get the lock then
|
||||||
@ -1546,6 +1556,12 @@ def install(self, **kwargs):
|
|||||||
|
|
||||||
self._update_failed(task, True, exc)
|
self._update_failed(task, True, exc)
|
||||||
|
|
||||||
|
if fail_fast:
|
||||||
|
# The user requested the installation to terminate on
|
||||||
|
# failure.
|
||||||
|
raise InstallError('{0}: {1}'
|
||||||
|
.format(fail_fast_err, str(exc)))
|
||||||
|
|
||||||
if pkg_id == self.pkg_id:
|
if pkg_id == self.pkg_id:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -718,7 +718,7 @@ def test_install_failed(install_mockery, monkeypatch, capsys):
|
|||||||
assert 'Warning: b failed to install' in out
|
assert 'Warning: b failed to install' in out
|
||||||
|
|
||||||
|
|
||||||
def test_install_fail_on_interrupt(install_mockery, monkeypatch, capsys):
|
def test_install_fail_on_interrupt(install_mockery, monkeypatch):
|
||||||
"""Test ctrl-c interrupted install."""
|
"""Test ctrl-c interrupted install."""
|
||||||
err_msg = 'mock keyboard interrupt'
|
err_msg = 'mock keyboard interrupt'
|
||||||
|
|
||||||
@ -733,9 +733,44 @@ def _interrupt(installer, task, **kwargs):
|
|||||||
with pytest.raises(KeyboardInterrupt, match=err_msg):
|
with pytest.raises(KeyboardInterrupt, match=err_msg):
|
||||||
installer.install()
|
installer.install()
|
||||||
|
|
||||||
|
|
||||||
|
def test_install_fail_fast_on_detect(install_mockery, monkeypatch, capsys):
|
||||||
|
"""Test fail_fast install when an install failure is detected."""
|
||||||
|
spec, installer = create_installer('a')
|
||||||
|
|
||||||
|
# Make sure the package is identified as failed
|
||||||
|
#
|
||||||
|
# This will prevent b from installing, which will cause the build of a
|
||||||
|
# to be skipped.
|
||||||
|
monkeypatch.setattr(spack.database.Database, 'prefix_failed', _true)
|
||||||
|
|
||||||
|
with pytest.raises(spack.installer.InstallError):
|
||||||
|
installer.install(fail_fast=True)
|
||||||
|
|
||||||
out = str(capsys.readouterr())
|
out = str(capsys.readouterr())
|
||||||
assert 'Failed to install' in out
|
assert 'Skipping build of a' in out
|
||||||
assert err_msg in out
|
|
||||||
|
|
||||||
|
def test_install_fail_fast_on_except(install_mockery, monkeypatch, capsys):
|
||||||
|
"""Test fail_fast install when an install failure results from an error."""
|
||||||
|
err_msg = 'mock patch failure'
|
||||||
|
|
||||||
|
def _patch(installer, task, **kwargs):
|
||||||
|
raise RuntimeError(err_msg)
|
||||||
|
|
||||||
|
spec, installer = create_installer('a')
|
||||||
|
|
||||||
|
# Raise a non-KeyboardInterrupt exception to trigger fast failure.
|
||||||
|
#
|
||||||
|
# This will prevent b from installing, which will cause the build of a
|
||||||
|
# to be skipped.
|
||||||
|
monkeypatch.setattr(spack.package.PackageBase, 'do_patch', _patch)
|
||||||
|
|
||||||
|
with pytest.raises(spack.installer.InstallError, matches=err_msg):
|
||||||
|
installer.install(fail_fast=True)
|
||||||
|
|
||||||
|
out = str(capsys.readouterr())
|
||||||
|
assert 'Skipping build of a' in out
|
||||||
|
|
||||||
|
|
||||||
def test_install_lock_failures(install_mockery, monkeypatch, capfd):
|
def test_install_lock_failures(install_mockery, monkeypatch, capfd):
|
||||||
|
@ -962,7 +962,7 @@ _spack_info() {
|
|||||||
_spack_install() {
|
_spack_install() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --no-check-signature --show-log-on-error --source -n --no-checksum -v --verbose --fake --only-concrete -f --file --clean --dirty --test --run-tests --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all"
|
SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --fail-fast --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --no-check-signature --show-log-on-error --source -n --no-checksum -v --verbose --fake --only-concrete -f --file --clean --dirty --test --run-tests --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all"
|
||||||
else
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user