summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/basic_usage.rst46
-rw-r--r--lib/spack/docs/packaging_guide.rst2
-rw-r--r--lib/spack/spack/cmd/gc.py47
-rw-r--r--lib/spack/spack/cmd/uninstall.py26
-rw-r--r--lib/spack/spack/database.py24
-rw-r--r--lib/spack/spack/test/cmd/gc.py44
-rw-r--r--lib/spack/spack/test/database.py11
7 files changed, 193 insertions, 7 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index d66745e4a0..3ad83c57bd 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -232,6 +232,50 @@ remove dependent packages *before* removing their dependencies or use the
.. _nondownloadable:
+^^^^^^^^^^^^^^^^^^
+Garbage collection
+^^^^^^^^^^^^^^^^^^
+
+When Spack builds software from sources, if often installs tools that are needed
+just to build or test other software. These are not necessary at runtime.
+To support cases where removing these tools can be a benefit Spack provides
+the ``spack gc`` ("garbage collector") command, which will uninstall all unneeded packages:
+
+.. code-block:: console
+
+ $ spack find
+ ==> 24 installed packages
+ -- linux-ubuntu18.04-broadwell / gcc@9.0.1 ----------------------
+ autoconf@2.69 findutils@4.6.0 libiconv@1.16 libszip@2.1.1 m4@1.4.18 openjpeg@2.3.1 pkgconf@1.6.3 util-macros@1.19.1
+ automake@1.16.1 gdbm@1.18.1 libpciaccess@0.13.5 libtool@2.4.6 mpich@3.3.2 openssl@1.1.1d readline@8.0 xz@5.2.4
+ cmake@3.16.1 hdf5@1.10.5 libsigsegv@2.12 libxml2@2.9.9 ncurses@6.1 perl@5.30.0 texinfo@6.5 zlib@1.2.11
+
+ $ spack gc
+ ==> The following packages will be uninstalled:
+
+ -- linux-ubuntu18.04-broadwell / gcc@9.0.1 ----------------------
+ vn47edz autoconf@2.69 6m3f2qn findutils@4.6.0 ubl6bgk libtool@2.4.6 pksawhz openssl@1.1.1d urdw22a readline@8.0
+ ki6nfw5 automake@1.16.1 fklde6b gdbm@1.18.1 b6pswuo m4@1.4.18 k3s2csy perl@5.30.0 lp5ya3t texinfo@6.5
+ ylvgsov cmake@3.16.1 5omotir libsigsegv@2.12 leuzbbh ncurses@6.1 5vmfbrq pkgconf@1.6.3 5bmv4tg util-macros@1.19.1
+
+ ==> Do you want to proceed? [y/N] y
+
+ [ ... ]
+
+ $ spack find
+ ==> 9 installed packages
+ -- linux-ubuntu18.04-broadwell / gcc@9.0.1 ----------------------
+ hdf5@1.10.5 libiconv@1.16 libpciaccess@0.13.5 libszip@2.1.1 libxml2@2.9.9 mpich@3.3.2 openjpeg@2.3.1 xz@5.2.4 zlib@1.2.11
+
+In the example above Spack went through all the packages in the DB
+and removed everything that is not either:
+
+1. A package installed upon explicit request of the user
+2. A ``link`` or ``run`` dependency, even transitive, of one of the packages at point 1.
+
+You can check :ref:`cmd-spack-find-metadata` to see how to query for explicitly installed packages
+or :ref:`dependency-types` for a more thorough treatment of dependency types.
+
^^^^^^^^^^^^^^^^^^^^^^^^^
Non-Downloadable Tarballs
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -414,6 +458,8 @@ Packages are divided into groups according to their architecture and
compiler. Within each group, Spack tries to keep the view simple, and
only shows the version of installed packages.
+.. _cmd-spack-find-metadata:
+
""""""""""""""""""""""""""""""""
Viewing more metadata
""""""""""""""""""""""""""""""""
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index 1d701c8551..223c509ac1 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -1950,6 +1950,8 @@ issues with 1.64.0, 1.65.0, and 1.66.0, you can say:
depends_on('boost@1.59.0:1.63,1.65.1,1.67.0:')
+.. _dependency-types:
+
^^^^^^^^^^^^^^^^
Dependency types
^^^^^^^^^^^^^^^^
diff --git a/lib/spack/spack/cmd/gc.py b/lib/spack/spack/cmd/gc.py
new file mode 100644
index 0000000000..ca9d88169a
--- /dev/null
+++ b/lib/spack/spack/cmd/gc.py
@@ -0,0 +1,47 @@
+# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import llnl.util.tty as tty
+
+import spack.cmd.common.arguments
+import spack.cmd.uninstall
+import spack.environment
+import spack.store
+
+description = "remove specs that are now no longer needed"
+section = "build"
+level = "short"
+
+
+def setup_parser(subparser):
+ spack.cmd.common.arguments.add_common_arguments(subparser, ['yes_to_all'])
+
+
+def gc(parser, args):
+ specs = spack.store.db.unused_specs
+
+ # Restrict garbage collection to the active environment
+ # speculating over roots that are yet to be installed
+ env = spack.environment.get_env(args=None, cmd_name='gc')
+ if env:
+ msg = 'Restricting the garbage collection to the "{0}" environment'
+ tty.msg(msg.format(env.name))
+ env.concretize()
+ roots = [s for s in env.roots()]
+ all_hashes = set([s.dag_hash() for r in roots for s in r.traverse()])
+ lr_hashes = set([s.dag_hash() for r in roots
+ for s in r.traverse(deptype=('link', 'run'))])
+ maybe_to_be_removed = all_hashes - lr_hashes
+ specs = [s for s in specs if s.dag_hash() in maybe_to_be_removed]
+
+ if not specs:
+ msg = "There are no unused specs. Spack's store is clean."
+ tty.msg(msg)
+ return
+
+ if not args.yes_to_all:
+ spack.cmd.uninstall.confirm_removal(specs)
+
+ spack.cmd.uninstall.do_uninstall(None, specs, force=False)
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index df69330b96..906da8d3b3 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -6,6 +6,7 @@
from __future__ import print_function
import argparse
+import sys
import spack.cmd
import spack.environment as ev
@@ -31,8 +32,8 @@ error_message = """You can either:
# Arguments for display_specs when we find ambiguity
display_args = {
'long': True,
- 'show_flags': True,
- 'variants': True,
+ 'show_flags': False,
+ 'variants': False,
'indent': 4,
}
@@ -324,11 +325,7 @@ def uninstall_specs(args, specs):
return
if not args.yes_to_all:
- tty.msg('The following packages will be uninstalled:\n')
- spack.cmd.display_specs(anything_to_do, **display_args)
- answer = tty.get_yes_or_no('Do you want to proceed?', default=False)
- if not answer:
- tty.die('Will not uninstall any packages.')
+ confirm_removal(anything_to_do)
# just force-remove things in the remove list
for spec in remove_list:
@@ -338,6 +335,21 @@ def uninstall_specs(args, specs):
do_uninstall(env, uninstall_list, args.force)
+def confirm_removal(specs):
+ """Display the list of specs to be removed and ask for confirmation.
+
+ Args:
+ specs (list): specs to be removed
+ """
+ tty.msg('The following packages will be uninstalled:\n')
+ spack.cmd.display_specs(specs, **display_args)
+ print('')
+ answer = tty.get_yes_or_no('Do you want to proceed?', default=False)
+ if not answer:
+ tty.msg('Aborting uninstallation')
+ sys.exit(0)
+
+
def uninstall(parser, args):
if not args.packages and not args.all:
tty.die('uninstall requires at least one package argument.',
diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py
index e219b0ad71..22fc266b5b 100644
--- a/lib/spack/spack/database.py
+++ b/lib/spack/spack/database.py
@@ -1295,6 +1295,30 @@ class Database(object):
upstream, record = self.query_by_spec_hash(key)
return record and not record.installed
+ @property
+ def unused_specs(self):
+ """Return all the specs that are currently installed but not needed
+ at runtime to satisfy user's requests.
+
+ Specs in the return list are those which are not either:
+ 1. Installed on an explicit user request
+ 2. Installed as a "run" or "link" dependency (even transitive) of
+ a spec at point 1.
+ """
+ needed, visited = set(), set()
+ with self.read_transaction():
+ for key, rec in self._data.items():
+ if rec.explicit:
+ # recycle `visited` across calls to avoid
+ # redundantly traversing
+ for spec in rec.spec.traverse(visited=visited):
+ needed.add(spec.dag_hash())
+
+ unused = [rec.spec for key, rec in self._data.items()
+ if key not in needed and rec.installed]
+
+ return unused
+
class UpstreamDatabaseLockingError(SpackError):
"""Raised when an operation would need to lock an upstream database"""
diff --git a/lib/spack/spack/test/cmd/gc.py b/lib/spack/spack/test/cmd/gc.py
new file mode 100644
index 0000000000..76eb608cf2
--- /dev/null
+++ b/lib/spack/spack/test/cmd/gc.py
@@ -0,0 +1,44 @@
+# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import pytest
+
+import spack.environment as ev
+import spack.spec
+import spack.main
+
+gc = spack.main.SpackCommand('gc')
+
+
+@pytest.mark.db
+def test_no_packages_to_remove(config, mutable_database, capsys):
+ with capsys.disabled():
+ output = gc('-y')
+ assert 'There are no unused specs.' in output
+
+
+@pytest.mark.db
+def test_packages_are_removed(config, mutable_database, capsys):
+ s = spack.spec.Spec('simple-inheritance')
+ s.concretize()
+ s.package.do_install(fake=True, explicit=True)
+ with capsys.disabled():
+ output = gc('-y')
+ assert 'Successfully uninstalled cmake' in output
+
+
+@pytest.mark.db
+def test_gc_with_environment(config, mutable_database, capsys):
+ s = spack.spec.Spec('simple-inheritance')
+ s.concretize()
+ s.package.do_install(fake=True, explicit=True)
+
+ e = ev.create('test_gc')
+ e.add('cmake')
+ with e:
+ with capsys.disabled():
+ output = gc('-y')
+ assert 'Restricting the garbage collection' in output
+ assert 'There are no unused specs' in output
diff --git a/lib/spack/spack/test/database.py b/lib/spack/spack/test/database.py
index e856b8f588..24a219a491 100644
--- a/lib/spack/spack/test/database.py
+++ b/lib/spack/spack/test/database.py
@@ -706,3 +706,14 @@ def test_uninstall_by_spec(mutable_database):
else:
mutable_database.remove(spec)
assert len(mutable_database.query()) == 0
+
+
+def test_query_unused_specs(mutable_database):
+ # This spec installs a fake cmake as a build only dependency
+ s = spack.spec.Spec('simple-inheritance')
+ s.concretize()
+ s.package.do_install(fake=True, explicit=True)
+
+ unused = spack.store.db.unused_specs
+ assert len(unused) == 1
+ assert unused[0].name == 'cmake'