summaryrefslogtreecommitdiff
path: root/lib/spack/spack/cmd/gc.py
blob: ff352db78a6300c557e4101f4c86e2c93e90a1cc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Copyright 2013-2023 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.common.confirmation
import spack.cmd.uninstall
import spack.deptypes as dt
import spack.environment as ev
import spack.store

description = "remove specs that are now no longer needed"
section = "build"
level = "short"


def setup_parser(subparser):
    subparser.add_argument(
        "-E",
        "--except-any-environment",
        action="store_true",
        help="remove everything unless needed by an environment",
    )
    subparser.add_argument(
        "-e",
        "--except-environment",
        metavar="ENV",
        action="append",
        default=[],
        help="remove everything unless needed by specified environment\n"
        "you can list multiple environments, or specify directory\n"
        "environments by path.",
    )
    subparser.add_argument(
        "-b",
        "--keep-build-dependencies",
        action="store_true",
        help="do not remove installed build-only dependencies of roots\n"
        "(default is to keep only link & run dependencies)",
    )
    spack.cmd.common.arguments.add_common_arguments(subparser, ["yes_to_all"])


def roots_from_environments(args, active_env):
    # if we're using -E or -e, make a list of environments whose roots we should consider.
    all_environments = []

    # -E will garbage collect anything not needed by any env, including the current one
    if args.except_any_environment:
        all_environments += list(ev.all_environments())
        if active_env:
            all_environments.append(active_env)

    # -e says "also preserve things needed by this particular env"
    for env_name_or_dir in args.except_environment:
        print("HMM", env_name_or_dir)
        if ev.exists(env_name_or_dir):
            env = ev.read(env_name_or_dir)
        elif ev.is_env_dir(env_name_or_dir):
            env = ev.Environment(env_name_or_dir)
        else:
            tty.die(f"No such environment: '{env_name_or_dir}'")
        all_environments.append(env)

    # add root hashes from all considered environments to list of roots
    root_hashes = set()
    for env in all_environments:
        root_hashes |= set(env.concretized_order)

    return root_hashes


def gc(parser, args):
    deptype = dt.LINK | dt.RUN
    if args.keep_build_dependencies:
        deptype |= dt.BUILD

    active_env = ev.active_environment()

    # wrap the whole command with a read transaction to avoid multiple
    with spack.store.STORE.db.read_transaction():
        if args.except_environment or args.except_any_environment:
            # if either of these is specified, we ignore the active environment and garbage
            # collect anything NOT in specified environments.
            root_hashes = roots_from_environments(args, active_env)

        elif active_env:
            # only gc what's in current environment
            tty.msg(f"Restricting garbage collection to environment '{active_env.name}'")
            root_hashes = set(spack.store.STORE.db.all_hashes())  # keep everything
            root_hashes -= set(active_env.all_hashes())  # except this env
            root_hashes |= set(active_env.concretized_order)  # but keep its roots
        else:
            # consider all explicit specs roots (the default for db.unused_specs())
            root_hashes = None

        specs = spack.store.STORE.db.unused_specs(root_hashes=root_hashes, deptype=deptype)
        if not specs:
            tty.msg("There are no unused specs. Spack's store is clean.")
            return

        if not args.yes_to_all:
            spack.cmd.common.confirmation.confirm_action(specs, "uninstalled", "uninstall")

        spack.cmd.uninstall.do_uninstall(specs, force=False)