summaryrefslogtreecommitdiff
path: root/lib/spack/spack/cmd/uninstall.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/cmd/uninstall.py')
-rw-r--r--lib/spack/spack/cmd/uninstall.py191
1 files changed, 140 insertions, 51 deletions
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index 350ef372cb..1ff3d8db5f 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -23,19 +23,33 @@
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
from __future__ import print_function
-import sys
+
import argparse
import llnl.util.tty as tty
-from llnl.util.tty.colify import colify
-
import spack
import spack.cmd
import spack.repository
from spack.cmd.find import display_specs
-from spack.package import PackageStillNeededError
-description="Remove an installed package"
+description = "Remove an installed package"
+
+error_message = """You can either:
+ a) Use a more specific spec, or
+ b) use spack uninstall -a to uninstall ALL matching specs.
+"""
+
+
+def ask_for_confirmation(message):
+ while True:
+ tty.msg(message + '[y/n]')
+ choice = raw_input().lower()
+ if choice == 'y':
+ break
+ elif choice == 'n':
+ raise SystemExit('Operation aborted')
+ tty.warn('Please reply either "y" or "n"')
+
def setup_parser(subparser):
subparser.add_argument(
@@ -44,10 +58,101 @@ def setup_parser(subparser):
subparser.add_argument(
'-a', '--all', action='store_true', dest='all',
help="USE CAREFULLY. Remove ALL installed packages that match each " +
- "supplied spec. i.e., if you say uninstall libelf, ALL versions of " +
- "libelf are uninstalled. This is both useful and dangerous, like rm -r.")
+ "supplied spec. i.e., if you say uninstall libelf, ALL versions of " +
+ "libelf are uninstalled. This is both useful and dangerous, like rm -r.")
subparser.add_argument(
- 'packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall")
+ '-d', '--dependents', action='store_true', dest='dependents',
+ help='Also uninstall any packages that depend on the ones given via command line.'
+ )
+ subparser.add_argument(
+ '-y', '--yes-to-all', action='store_true', dest='yes_to_all',
+ help='Assume "yes" is the answer to every confirmation asked to the user.'
+
+ )
+ subparser.add_argument('packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall")
+
+
+def concretize_specs(specs, allow_multiple_matches=False, force=False):
+ """
+ Returns a list of specs matching the non necessarily concretized specs given from cli
+
+ Args:
+ specs: list of specs to be matched against installed packages
+ allow_multiple_matches : boolean (if True multiple matches for each item in specs are admitted)
+
+ Return:
+ list of specs
+ """
+ specs_from_cli = [] # List of specs that match expressions given via command line
+ has_errors = False
+ for spec in specs:
+ matching = spack.installed_db.query(spec)
+ # For each spec provided, make sure it refers to only one package.
+ # Fail and ask user to be unambiguous if it doesn't
+ if not allow_multiple_matches and len(matching) > 1:
+ tty.error("%s matches multiple packages:" % spec)
+ print()
+ display_specs(matching, long=True)
+ print()
+ has_errors = True
+
+ # No installed package matches the query
+ if len(matching) == 0 and not force:
+ tty.error("%s does not match any installed packages." % spec)
+ has_errors = True
+
+ specs_from_cli.extend(matching)
+ if has_errors:
+ tty.die(error_message)
+
+ return specs_from_cli
+
+
+def installed_dependents(specs):
+ """
+ Returns a dictionary that maps a spec with a list of its installed dependents
+
+ Args:
+ specs: list of specs to be checked for dependents
+
+ Returns:
+ dictionary of installed dependents
+ """
+ dependents = {}
+ for item in specs:
+ lst = [x for x in item.package.installed_dependents if x not in specs]
+ if lst:
+ lst = list(set(lst))
+ dependents[item] = lst
+ return dependents
+
+
+def do_uninstall(specs, force):
+ """
+ Uninstalls all the specs in a list.
+
+ Args:
+ specs: list of specs to be uninstalled
+ force: force uninstallation (boolean)
+ """
+ packages = []
+ for item in specs:
+ try:
+ # should work if package is known to spack
+ packages.append(item.package)
+ except spack.repository.UnknownPackageError as e:
+ # The package.py file has gone away -- but still
+ # want to uninstall.
+ spack.Package(item).do_uninstall(force=True)
+
+ # Sort packages to be uninstalled by the number of installed dependents
+ # This ensures we do things in the right order
+ def num_installed_deps(pkg):
+ return len(pkg.installed_dependents)
+
+ packages.sort(key=num_installed_deps)
+ for item in packages:
+ item.do_uninstall(force=force)
def uninstall(parser, args):
@@ -56,50 +161,34 @@ def uninstall(parser, args):
with spack.installed_db.write_transaction():
specs = spack.cmd.parse_specs(args.packages)
+ # Gets the list of installed specs that match the ones give via cli
+ uninstall_list = concretize_specs(specs, args.all, args.force) # takes care of '-a' is given in the cli
+ dependent_list = installed_dependents(uninstall_list) # takes care of '-d'
- # For each spec provided, make sure it refers to only one package.
- # Fail and ask user to be unambiguous if it doesn't
- pkgs = []
- for spec in specs:
- matching_specs = spack.installed_db.query(spec)
- if not args.all and len(matching_specs) > 1:
- tty.error("%s matches multiple packages:" % spec)
- print()
- display_specs(matching_specs, long=True)
- print()
- print("You can either:")
- print(" a) Use a more specific spec, or")
- print(" b) use spack uninstall -a to uninstall ALL matching specs.")
- sys.exit(1)
-
- if len(matching_specs) == 0:
- if args.force: continue
- tty.die("%s does not match any installed packages." % spec)
-
- for s in matching_specs:
- try:
- # should work if package is known to spack
- pkgs.append(s.package)
- except spack.repository.UnknownPackageError as e:
- # The package.py file has gone away -- but still
- # want to uninstall.
- spack.Package(s).do_uninstall(force=True)
-
- # Sort packages to be uninstalled by the number of installed dependents
- # This ensures we do things in the right order
- def num_installed_deps(pkg):
- return len(pkg.installed_dependents)
- pkgs.sort(key=num_installed_deps)
-
- # Uninstall packages in order now.
- for pkg in pkgs:
- try:
- pkg.do_uninstall(force=args.force)
- except PackageStillNeededError as e:
- tty.error("Will not uninstall %s" % e.spec.format("$_$@$%@$#", color=True))
+ # Process dependent_list and update uninstall_list
+ has_error = False
+ if dependent_list and not args.dependents and not args.force:
+ for spec, lst in dependent_list.items():
+ tty.error("Will not uninstall %s" % spec.format("$_$@$%@$#", color=True))
print('')
print("The following packages depend on it:")
- display_specs(e.dependents, long=True)
+ display_specs(lst, long=True)
print('')
- print("You can use spack uninstall -f to force this action.")
- sys.exit(1)
+ has_error = True
+ elif args.dependents:
+ for key, lst in dependent_list.items():
+ uninstall_list.extend(lst)
+ uninstall_list = list(set(uninstall_list))
+
+ if has_error:
+ tty.die('You can use spack uninstall --dependents to uninstall these dependencies as well')
+
+ if not args.yes_to_all:
+ tty.msg("The following packages will be uninstalled : ")
+ print('')
+ display_specs(uninstall_list, long=True)
+ print('')
+ ask_for_confirmation('Do you want to proceed ? ')
+
+ # Uninstall everything on the list
+ do_uninstall(uninstall_list, args.force)