summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTamara Dahlgren <dahlgren1@llnl.gov>2020-03-09 15:52:13 -0700
committerTamara Dahlgren <35777542+tldahlgren@users.noreply.github.com>2020-06-23 10:22:41 -0700
commit96932d65a819a08fe40dd4120b0ed05ed8011e01 (patch)
treea87b8a95529852f4e07e4ecbad997d30640abe57 /lib
parentf54a8a77b46dbbed97243a7d1d627d6f581f0b91 (diff)
downloadspack-96932d65a819a08fe40dd4120b0ed05ed8011e01.tar.gz
spack-96932d65a819a08fe40dd4120b0ed05ed8011e01.tar.bz2
spack-96932d65a819a08fe40dd4120b0ed05ed8011e01.tar.xz
spack-96932d65a819a08fe40dd4120b0ed05ed8011e01.zip
Added support for --fail-fast install option to terminate on first failure
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/packaging_guide.rst11
-rw-r--r--lib/spack/spack/cmd/install.py4
-rw-r--r--lib/spack/spack/installer.py16
-rw-r--r--lib/spack/spack/test/installer.py41
4 files changed, 67 insertions, 5 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index 840c29454b..1f246c0faa 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -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
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:
.. code-block:: console
$ spack edit mypackage
- $ spack install mypackage
+ $ spack install --fail-fast mypackage
... build breaks! ...
$ spack clean mypackage
$ spack edit mypackage
- $ spack install mypackage
+ $ spack install --fail-fast mypackage
... repeat clean/install until install works ...
Below are some commands that will allow you some finer-grained
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index e2a6327f5f..10eb3c327f 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -32,6 +32,7 @@ def update_kwargs_from_args(args, kwargs):
that will be passed to Package.do_install API"""
kwargs.update({
+ 'fail_fast': args.fail_fast,
'keep_prefix': args.keep_prefix,
'keep_stage': args.keep_stage,
'restage': not args.dont_restage,
@@ -79,6 +80,9 @@ the dependencies"""
'--overwrite', action='store_true',
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(
'--keep-prefix', action='store_true',
help="don't remove the install prefix if installation fails")
subparser.add_argument(
diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py
index 786eec5383..cd2aa00bd0 100644
--- a/lib/spack/spack/installer.py
+++ b/lib/spack/spack/installer.py
@@ -549,6 +549,9 @@ install_args_docstring = """
dirty (bool): Don't clean the build environment before installing.
explicit (bool): True if package was explicitly installed, False
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.
force (bool): Install again, even if already installed.
install_deps (bool): Install dependencies before installing this
@@ -1385,11 +1388,14 @@ class PackageInstaller(object):
Args:"""
+ fail_fast = kwargs.get('fail_fast', False)
install_deps = kwargs.get('install_deps', True)
keep_prefix = kwargs.get('keep_prefix', False)
keep_stage = kwargs.get('keep_stage', 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
# always installed regardless of whether the root was installed
install_package = kwargs.pop('install_package', True)
@@ -1449,6 +1455,10 @@ class PackageInstaller(object):
if pkg_id in self.failed or spack.store.db.prefix_failed(spec):
tty.warn('{0} failed to install'.format(pkg_id))
self._update_failed(task)
+
+ if fail_fast:
+ raise InstallError(fail_fast_err)
+
continue
# Attempt to get a write lock. If we can't get the lock then
@@ -1546,6 +1556,12 @@ class PackageInstaller(object):
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:
raise
diff --git a/lib/spack/spack/test/installer.py b/lib/spack/spack/test/installer.py
index 96612c8f5f..89efbe4fbe 100644
--- a/lib/spack/spack/test/installer.py
+++ b/lib/spack/spack/test/installer.py
@@ -718,7 +718,7 @@ def test_install_failed(install_mockery, monkeypatch, capsys):
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."""
err_msg = 'mock keyboard interrupt'
@@ -733,9 +733,44 @@ def test_install_fail_on_interrupt(install_mockery, monkeypatch, capsys):
with pytest.raises(KeyboardInterrupt, match=err_msg):
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())
+ assert 'Skipping build of a' 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 'Failed to install' in out
- assert err_msg in out
+ assert 'Skipping build of a' in out
def test_install_lock_failures(install_mockery, monkeypatch, capfd):