summaryrefslogtreecommitdiff
path: root/lib/spack/spack/cmd/deprecate.py
blob: 216095cb8ed322ad95ff1436c5cef65d2d20147a (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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Copyright 2013-2021 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)
'''Deprecate one Spack install in favor of another

Spack packages of different configurations cannot be installed to the same
location. However, in some circumstances (e.g. security patches) old
installations should never be used again. In these cases, we will mark the old
installation as deprecated, remove it, and link another installation into its
place.

It is up to the user to ensure binary compatibility between the deprecated
installation and its deprecator.
'''
from __future__ import print_function

import argparse
import os

import llnl.util.tty as tty

import spack.cmd
import spack.cmd.common.arguments as arguments
import spack.environment as ev
import spack.store
from spack.database import InstallStatuses
from spack.error import SpackError

description = "Replace one package with another via symlinks"
section = "admin"
level = "long"

# Arguments for display_specs when we find ambiguity
display_args = {
    'long': True,
    'show_flags': True,
    'variants': True,
    'indent': 4,
}


def setup_parser(sp):
    setup_parser.parser = sp

    arguments.add_common_arguments(sp, ['yes_to_all'])

    deps = sp.add_mutually_exclusive_group()
    deps.add_argument('-d', '--dependencies', action='store_true',
                      default=True, dest='dependencies',
                      help='Deprecate dependencies (default)')
    deps.add_argument('-D', '--no-dependencies', action='store_false',
                      default=True, dest='dependencies',
                      help='Do not deprecate dependencies')

    install = sp.add_mutually_exclusive_group()
    install.add_argument('-i', '--install-deprecator', action='store_true',
                         default=False, dest='install',
                         help='Concretize and install deprecator spec')
    install.add_argument('-I', '--no-install-deprecator',
                         action='store_false', default=False, dest='install',
                         help='Deprecator spec must already be installed (default)')  # noqa 501

    sp.add_argument('-l', '--link-type', type=str,
                    default='soft', choices=['soft', 'hard'],
                    help="Type of filesystem link to use for deprecation (default soft)")  # noqa 501

    sp.add_argument('specs', nargs=argparse.REMAINDER,
                    help="spec to deprecate and spec to use as deprecator")


def deprecate(parser, args):
    """Deprecate one spec in favor of another"""
    env = ev.active_environment()
    specs = spack.cmd.parse_specs(args.specs)

    if len(specs) != 2:
        raise SpackError('spack deprecate requires exactly two specs')

    install_query = [InstallStatuses.INSTALLED, InstallStatuses.DEPRECATED]
    deprecate = spack.cmd.disambiguate_spec(specs[0], env, local=True,
                                            installed=install_query)

    if args.install:
        deprecator = specs[1].concretized()
    else:
        deprecator = spack.cmd.disambiguate_spec(specs[1], env, local=True)

    # calculate all deprecation pairs for errors and warning message
    all_deprecate = []
    all_deprecators = []

    generator = deprecate.traverse(
        order='post', type='link', root=True
    ) if args.dependencies else [deprecate]
    for spec in generator:
        all_deprecate.append(spec)
        all_deprecators.append(deprecator[spec.name])
        # This will throw a key error if deprecator does not have a dep
        # that matches the name of a dep of the spec

    if not args.yes_to_all:
        tty.msg('The following packages will be deprecated:\n')
        spack.cmd.display_specs(all_deprecate, **display_args)
        tty.msg("In favor of (respectively):\n")
        spack.cmd.display_specs(all_deprecators, **display_args)
        print()

        already_deprecated = []
        already_deprecated_for = []
        for spec in all_deprecate:
            deprecated_for = spack.store.db.deprecator(spec)
            if deprecated_for:
                already_deprecated.append(spec)
                already_deprecated_for.append(deprecated_for)

        tty.msg('The following packages are already deprecated:\n')
        spack.cmd.display_specs(already_deprecated, **display_args)
        tty.msg('In favor of (respectively):\n')
        spack.cmd.display_specs(already_deprecated_for, **display_args)

        answer = tty.get_yes_or_no('Do you want to proceed?', default=False)
        if not answer:
            tty.die('Will not deprecate any packages.')

    link_fn = os.link if args.link_type == 'hard' else os.symlink

    for dcate, dcator in zip(all_deprecate, all_deprecators):
        dcate.package.do_deprecate(dcator, link_fn)