Repository: spack/spack

Issue:
Should we merge spack clean and spack purge?
I've talked to some users who mix up `spack clean` and `spack purge`. They basically do the same thing: one deletes the stage directory for a package spec (or multiple package specs) and the other deletes the stage directory for all packages. In light of #2867, should we just merge these into a single command?


Discussion:
What about adding an optional `[anonymous-spec]` at the end of `spack purge` to restrict the cleaning to what matches and remove `spack clean`?
That's what I was thinking. I think I like the name `clean` better than `purge` though. But yeah, it would be really easy to do, I'm just wondering if others agree.
I like `clean` better too, but @alalazo's suggestion is the way to do it.  I think the spec argument should be mutually exclusive with the other options.

@alalazo: why `anonymous`?  Could be any spec, right?



Resolving patch:
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -29,21 +29,60 @@
 import spack
 import spack.cmd
 
-description = "remove build stage and source tarball for packages"
+description = "remove temporary build files and/or downloaded archives"
 section = "build"
 level = "long"
 
 
+class AllClean(argparse.Action):
+    """Activates flags -s -d and -m simultaneously"""
+    def __call__(self, parser, namespace, values, option_string=None):
+        parser.parse_args(['-sdm'], namespace=namespace)
+
+
 def setup_parser(subparser):
-    subparser.add_argument('packages', nargs=argparse.REMAINDER,
-                           help="specs of packages to clean")
+    subparser.add_argument(
+        '-s', '--stage', action='store_true',
+        help="remove all temporary build stages (default)")
+    subparser.add_argument(
+        '-d', '--downloads', action='store_true',
+        help="remove cached downloads")
+    subparser.add_argument(
+        '-m', '--misc-cache', action='store_true',
+        help="remove long-lived caches, like the virtual package index")
+    subparser.add_argument(
+        '-a', '--all', action=AllClean, help="equivalent to -sdm", nargs=0
+    )
+    subparser.add_argument(
+        'specs',
+        nargs=argparse.REMAINDER,
+        help="removes the build stages and tarballs for specs"
+    )
 
 
 def clean(parser, args):
-    if not args.packages:
-        tty.die("spack clean requires at least one package spec.")
 
-    specs = spack.cmd.parse_specs(args.packages, concretize=True)
-    for spec in specs:
-        package = spack.repo.get(spec)
-        package.do_clean()
+    # If nothing was set, activate the default
+    if not any([args.specs, args.stage, args.downloads, args.misc_cache]):
+        args.stage = True
+
+    # Then do the cleaning falling through the cases
+    if args.specs:
+        specs = spack.cmd.parse_specs(args.specs, concretize=True)
+        for spec in specs:
+            msg = 'Cleaning build stage [{0}]'
+            tty.msg(msg.format(spec.short_spec))
+            package = spack.repo.get(spec)
+            package.do_clean()
+
+    if args.stage:
+        tty.msg('Removing all temporary build stages')
+        spack.stage.purge()
+
+    if args.downloads:
+        tty.msg('Removing cached downloads')
+        spack.fetch_cache.destroy()
+
+    if args.misc_cache:
+        tty.msg('Removing cached information on repositories')
+        spack.misc_cache.destroy()
diff --git a/lib/spack/spack/cmd/purge.py b/lib/spack/spack/cmd/purge.py
deleted file mode 100644
--- a/lib/spack/spack/cmd/purge.py
+++ /dev/null
@@ -1,60 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://github.com/llnl/spack
-# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import spack
-import spack.stage as stage
-
-description = "remove temporary build files and/or downloaded archives"
-section = "admin"
-level = "long"
-
-
-def setup_parser(subparser):
-    subparser.add_argument(
-        '-s', '--stage', action='store_true', default=True,
-        help="remove all temporary build stages (default)")
-    subparser.add_argument(
-        '-d', '--downloads', action='store_true',
-        help="remove cached downloads")
-    subparser.add_argument(
-        '-m', '--misc-cache', action='store_true',
-        help="remove long-lived caches, like the virtual package index")
-    subparser.add_argument(
-        '-a', '--all', action='store_true',
-        help="remove all of the above")
-
-
-def purge(parser, args):
-    # Special case: no flags.
-    if not any((args.stage, args.downloads, args.misc_cache, args.all)):
-        stage.purge()
-        return
-
-    # handle other flags with fall through.
-    if args.stage or args.all:
-        stage.purge()
-    if args.downloads or args.all:
-        spack.fetch_cache.destroy()
-    if args.misc_cache or args.all:
-        spack.misc_cache.destroy()


Test patch:
diff --git a/lib/spack/spack/test/cmd/clean.py b/lib/spack/spack/test/cmd/clean.py
new file mode 100644
--- /dev/null
+++ b/lib/spack/spack/test/cmd/clean.py
@@ -0,0 +1,68 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import pytest
+import spack
+import spack.main
+import spack.package
+
+clean = spack.main.SpackCommand('clean')
+
+
+@pytest.fixture()
+def mock_calls_for_clean(monkeypatch):
+
+    class Counter(object):
+        def __init__(self):
+            self.call_count = 0
+
+        def __call__(self, *args, **kwargs):
+            self.call_count += 1
+
+    monkeypatch.setattr(spack.package.PackageBase, 'do_clean', Counter())
+    monkeypatch.setattr(spack.stage, 'purge', Counter())
+    monkeypatch.setattr(spack.fetch_cache, 'destroy', Counter(), raising=False)
+    monkeypatch.setattr(spack.misc_cache, 'destroy', Counter())
+
+
+@pytest.mark.usefixtures(
+    'builtin_mock', 'config', 'mock_calls_for_clean'
+)
+@pytest.mark.parametrize('command_line,counters', [
+    ('mpileaks', [1, 0, 0, 0]),
+    ('-s', [0, 1, 0, 0]),
+    ('-sd', [0, 1, 1, 0]),
+    ('-a', [0, 1, 1, 1]),
+])
+def test_function_calls(command_line, counters):
+
+    # Call the command with the supplied command line
+    clean(command_line)
+
+    # Assert that we called the expected functions the correct
+    # number of times
+    assert spack.package.PackageBase.do_clean.call_count == counters[0]
+    assert spack.stage.purge.call_count == counters[1]
+    assert spack.fetch_cache.destroy.call_count == counters[2]
+    assert spack.misc_cache.destroy.call_count == counters[3]