aboutsummaryrefslogtreecommitdiff
blob: a5473a94f1a2dd8e641dc049a4f404b5ab003245 (plain)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# Copyright 2010 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

__all__ = ("VirtualsManager",)

from copy import deepcopy

from portage import os
from portage.dep import Atom
from portage.exception import InvalidAtom
from portage.localization import _
from portage.util import grabdict, stack_dictlist, writemsg
from portage.versions import cpv_getkey


class VirtualsManager:
    def __init__(self, *args, **kwargs):
        if kwargs.get("_copy"):
            return

        assert len(args) == 1, "VirtualsManager.__init__ takes one positional argument"
        assert not kwargs, (
            "unknown keyword argument(s) '%s' passed to VirtualsManager.__init__"
            % ", ".join(kwargs)
        )

        profiles = args[0]
        self._virtuals = None
        self._dirVirtuals = None
        self._virts_p = None

        # Virtuals obtained from the vartree
        self._treeVirtuals = None
        # Virtuals added by the depgraph via self.add_depgraph_virtuals().
        self._depgraphVirtuals = {}

        # Initialise _dirVirtuals.
        self._read_dirVirtuals(profiles)

        # We could initialise _treeVirtuals here, but some consumers want to
        # pass their own vartree.

    def _read_dirVirtuals(self, profiles):
        """
        Read the 'virtuals' file in all profiles.
        """
        virtuals_list = []
        for x in profiles:
            virtuals_file = os.path.join(x, "virtuals")
            virtuals_dict = grabdict(virtuals_file)
            atoms_dict = {}
            for k, v in virtuals_dict.items():
                try:
                    virt_atom = Atom(k)
                except InvalidAtom:
                    virt_atom = None
                else:
                    if virt_atom.blocker or str(virt_atom) != str(virt_atom.cp):
                        virt_atom = None
                if virt_atom is None:
                    writemsg(
                        _("--- Invalid virtuals atom in %s: %s\n") % (virtuals_file, k),
                        noiselevel=-1,
                    )
                    continue
                providers = []
                for atom in v:
                    atom_orig = atom
                    if atom[:1] == "-":
                        # allow incrementals
                        atom = atom[1:]
                    try:
                        atom = Atom(atom)
                    except InvalidAtom:
                        atom = None
                    else:
                        if atom.blocker:
                            atom = None
                    if atom is None:
                        writemsg(
                            _("--- Invalid atom in %s: %s\n")
                            % (virtuals_file, atom_orig),
                            noiselevel=-1,
                        )
                    else:
                        if atom_orig == str(atom):
                            # normal atom, so return as Atom instance
                            providers.append(atom)
                        else:
                            # atom has special prefix, so return as string
                            providers.append(atom_orig)
                if providers:
                    atoms_dict[virt_atom] = providers
            if atoms_dict:
                virtuals_list.append(atoms_dict)

        self._dirVirtuals = stack_dictlist(virtuals_list, incremental=True)

        for virt in self._dirVirtuals:
            # Preference for virtuals decreases from left to right.
            self._dirVirtuals[virt].reverse()

    def __deepcopy__(self, memo=None):
        if memo is None:
            memo = {}
        result = VirtualsManager(_copy=True)
        memo[id(self)] = result

        # immutable attributes (internal policy ensures lack of mutation)
        # _treeVirtuals is initilised by _populate_treeVirtuals().
        # Before that it's 'None'.
        result._treeVirtuals = self._treeVirtuals
        memo[id(self._treeVirtuals)] = self._treeVirtuals
        # _dirVirtuals is initilised by __init__.
        result._dirVirtuals = self._dirVirtuals
        memo[id(self._dirVirtuals)] = self._dirVirtuals

        # mutable attributes (change when add_depgraph_virtuals() is called)
        result._virtuals = deepcopy(self._virtuals, memo)
        result._depgraphVirtuals = deepcopy(self._depgraphVirtuals, memo)
        result._virts_p = deepcopy(self._virts_p, memo)

        return result

    def _compile_virtuals(self):
        """Stack installed and profile virtuals.  Preference for virtuals
        decreases from left to right.
        Order of preference:
        1. installed and in profile
        2. installed only
        3. profile only
        """

        assert self._treeVirtuals is not None, (
            "_populate_treeVirtuals() must be called before "
            + "any query about virtuals"
        )

        # Virtuals by profile+tree preferences.
        ptVirtuals = {}

        for virt, installed_list in self._treeVirtuals.items():
            profile_list = self._dirVirtuals.get(virt, None)
            if not profile_list:
                continue
            for cp in installed_list:
                if cp in profile_list:
                    ptVirtuals.setdefault(virt, [])
                    ptVirtuals[virt].append(cp)

        virtuals = stack_dictlist(
            [ptVirtuals, self._treeVirtuals, self._dirVirtuals, self._depgraphVirtuals]
        )
        self._virtuals = virtuals
        self._virts_p = None

    def getvirtuals(self):
        """
        Computes self._virtuals if necessary and returns it.
        self._virtuals is only computed on the first call.
        """
        if self._virtuals is None:
            self._compile_virtuals()

        return self._virtuals

    def _populate_treeVirtuals(self, vartree):
        """
        Initialize _treeVirtuals from the given vartree.
        It must not have been initialized already, otherwise
        our assumptions about immutability don't hold.
        """
        assert self._treeVirtuals is None, "treeVirtuals must not be reinitialized"

        self._treeVirtuals = {}

        for provide, cpv_list in vartree.get_all_provides().items():
            try:
                provide = Atom(provide)
            except InvalidAtom:
                continue
            self._treeVirtuals[provide.cp] = [Atom(cpv_getkey(cpv)) for cpv in cpv_list]

    def populate_treeVirtuals_if_needed(self, vartree):
        """
        Initialize _treeVirtuals if it hasn't been done already.
        This is a hack for consumers that already have an populated vartree.
        """
        if self._treeVirtuals is not None:
            return

        self._populate_treeVirtuals(vartree)

    def add_depgraph_virtuals(self, mycpv, virts):
        """This updates the preferences for old-style virtuals,
        affecting the behavior of dep_expand() and dep_check()
        calls. It can change dbapi.match() behavior since that
        calls dep_expand(). However, dbapi instances have
        internal match caches that are not invalidated when
        preferences are updated here. This can potentially
        lead to some inconsistency (relevant to bug #1343)."""

        # Ensure that self._virtuals is populated.
        if self._virtuals is None:
            self.getvirtuals()

        modified = False
        cp = Atom(cpv_getkey(mycpv))
        for virt in virts:
            try:
                virt = Atom(virt).cp
            except InvalidAtom:
                continue
            providers = self._virtuals.get(virt)
            if providers and cp in providers:
                continue
            providers = self._depgraphVirtuals.get(virt)
            if providers is None:
                providers = []
                self._depgraphVirtuals[virt] = providers
            if cp not in providers:
                providers.append(cp)
                modified = True

        if modified:
            self._compile_virtuals()

    def get_virts_p(self):
        if self._virts_p is not None:
            return self._virts_p

        virts = self.getvirtuals()
        virts_p = {}
        for x in virts:
            vkeysplit = x.split("/")
            if vkeysplit[1] not in virts_p:
                virts_p[vkeysplit[1]] = virts[x]
        self._virts_p = virts_p
        return virts_p