aboutsummaryrefslogtreecommitdiff
blob: 6d7cb4efdb25b4151731851ec79102819499e83d (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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
#!/usr/bin/python
#
# Copyright 2010 Brian Dolbec <brian.dolbec@gmail.com>
# Copyright(c) 2010, Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
#


"""Provides support functions to enalyze modules"""

from gentoolkit import errors
from gentoolkit.keyword import reduce_keywords
from gentoolkit.flag import (
    reduce_flags,
    get_flags,
    get_all_cpv_use,
    filter_flags,
    get_installed_use,
    defaulted_flags,
)

# from gentoolkit.package import Package

import portage


class FlagAnalyzer:
    """Specialty functions for analysing an installed package's
    USE flags.  Can be used for single or mulitple use without
    needing to be reset unless the system USE flags are changed.

    @type system: list or set
    @param system: the default system USE flags.
    @type _get_flags: function
    @param _get_flags: Normally defaulted, can be overriden for testing
    @type _get_used: function
    @param _get_used: Normally defaulted, can be overriden for testing
    """

    def __init__(
        self,
        system,
        filter_defaults=False,
        target="USE",
        _get_flags=get_flags,
        _get_used=get_installed_use,
    ):
        self.get_flags = _get_flags
        self.get_used = _get_used
        self.filter_defaults = filter_defaults
        self.target = target
        self.reset(system)

    def reset(self, system):
        """Resets the internal system USE flags and use_expand variables
        to the new setting. The use_expand variable is handled internally.

        @type system: list or set
        @param system: the default system USE flags.
        """
        self.system = set(system)
        self.use_expand = portage.settings["USE_EXPAND"].lower().split()

    def analyse_cpv(self, cpv):
        """Gets all relavent USE flag info for a cpv and breaks them down
        into 3 sets, plus (package.use enabled), minus ( package.use disabled),
        unset.

        @param cpv: string. 'cat/pkg-ver'
        @rtype tuple of sets
        @return (plus, minus, unset) sets of USE flags
        """
        installed = set(self.get_used(cpv, self.target))
        _iuse = self.get_flags(cpv)
        iuse = set(reduce_flags(_iuse))
        iuse_defaults = defaulted_flags(_iuse)
        return self._analyse(installed, iuse, iuse_defaults)

    def _analyse(self, installed, iuse, iuse_defaults):
        """Analyzes the supplied info and returns the flag settings
        that differ from the defaults

        @type installed: set
        @param installed: the installed with use flags
        @type iuse: set
        @param iuse: the current ebuilds IUSE
        """
        defaults = self.system.intersection(iuse)
        # update defaults with iuse_defaults
        defaults.update(iuse_defaults["+"])
        defaults = defaults.difference(iuse_defaults["-"])
        usedflags = iuse.intersection(set(installed))
        if self.filter_defaults:
            plus = usedflags.difference(defaults)
        else:
            plus = usedflags
        minus = defaults.difference(usedflags)
        unset = iuse.difference(defaults, plus, minus)
        cleaned_unset = self.remove_expanding(unset)
        return (plus, minus, cleaned_unset)

    def analyse_pkg(self, pkg):
        """Gets all relevent USE flag info for a pkg and breaks them down
        into 3 sets, plus (package.use enabled), minus ( package.use disabled),
        unset.

        @param pkg: gentoolkit.package.Package object
        @rtype tuple of sets
        @return (plus, minus, unset) sets of USE flags
        """
        installed = set(self.pkg_used(pkg))
        # print("installed =", installed)
        _iuse = self.pkg_flags(pkg)
        iuse = set(reduce_flags(_iuse))
        iuse_defaults = defaulted_flags(_iuse)
        # print("iuse =", iuse)
        return self._analyse(installed, iuse, iuse_defaults)

    def pkg_used(self, pkg):
        if self.target == "USE":
            return pkg.use().split()
        return pkg.environment(self.target).split()

    def pkg_flags(self, pkg):
        final_use, use_expand_hidden, usemasked, useforced = get_all_cpv_use(pkg.cpv)
        flags = pkg.environment("IUSE", prefer_vdb=False).split()
        return filter_flags(flags, use_expand_hidden, usemasked, useforced)

    def redundant(self, cpv, iuse):
        """Checks for redundant settings.
        future function. Not yet implemented.
        """
        pass

    def remove_expanding(self, flags):
        """Remove unwanted USE_EXPAND flags
        from unset IUSE sets

        @param flags: short list or set of USE flags
        @rtype set
        @return USE flags
        """
        _flags = set(flags)
        for expander in self.use_expand:
            for flag in flags:
                if expander in flag:
                    _flags.remove(flag)
            if not _flags:
                break
        return _flags


class KeywordAnalyser:
    """Specialty functions for analysing the installed package db for
    keyword useage and the packages that used them.

    Note: should be initialized with the internal set_order() before use.
    See internal set_order() for more details.
    This class of functions can be used for single cpv checks or
    used repeatedly for an entire package db.

    @type  arch: string
    @param arch: the system ARCH setting
    @type  accept_keywords: list
    @param accept_keywords: eg. ['x86', '~x86']
    @type  get_aux: function, defaults to: portage.db[portage.root]["vartree"].dbapi.aux_get
    @param vardb: vardb class of functions, needed=aux_get()
            to return => KEYWORDS & USE flags for a cpv
            = aux_get(cpv, ["KEYWORDS", "USE"])
    """

    # parsing order to determine appropriate keyword used for installation
    normal_order = ["stable", "testing", "prefix", "testing_prefix", "missing"]
    prefix_order = ["prefix", "testing_prefix", "stable", "testing", "missing"]
    parse_range = list(range(len(normal_order)))

    def __init__(
        self, arch, accept_keywords, vardb=portage.db[portage.root]["vartree"].dbapi
    ):
        self.arch = arch
        self.accept_keywords = accept_keywords
        self.vardb = vardb
        self.prefix = ""
        self.parse_order = None
        self.check_key = {
            "stable": self._stable,
            "testing": self._testing,
            "prefix": self._prefix,
            "testing_prefix": self._testing_prefix,
            "missing": self._missing,
        }
        self.mismatched = []

    def determine_keyword(self, keywords, used, cpv):
        """Determine the keyword from the installed USE flags and
        the KEYWORDS that was used to install a package.

        @param keywords: list of keywords available to install a pkg
        @param used: list of USE flalgs recorded for the installed pkg
        @rtype: string
        @return a keyword or null string
        """
        used = set(used)
        kwd = None
        result = ""
        if keywords:
            absolute_kwds = reduce_keywords(keywords)
            kwd = list(used.intersection(absolute_kwds))
            # if keywords == ['~ppc64']:
            # print "Checked keywords for kwd", keywords, used, "kwd =", kwd
        if not kwd:
            # print "Checking for kwd against portage.archlist"
            absolute_kwds = reduce_keywords(keywords)
            # check for one against archlist then re-check
            kwd = list(absolute_kwds.intersection(portage.archlist))
            # print "determined keyword =", kwd
        if len(kwd) == 1:
            key = kwd[0]
            # print "determined keyword =", key
        elif not kwd:
            # print "kwd != 1", kwd, cpv
            result = self._missing(self.keyword, keywords)
        else:  # too many, try to narrow them dowm
            # print "too many kwd's, trying to match against arch"
            _kwd = list(set(kwd).intersection(self.arch))
            key = ""
            if _kwd:
                # print "found one! :)", _kwd
                key = _kwd
            else:  # try re-running the short list against archlist
                # print "Checking kwd for _kwd against portage.archlist"
                _kwd = list(set(kwd).intersection(portage.archlist))
                if _kwd and len(_kwd) == 1:
                    # print "found one! :)", _kwd
                    key = _kwd[0]
                else:
                    # print " :( didn't work, _kwd =", _kwd, "giving up on:", cpv
                    result = self._missing(self.keyword, keywords)
        i = 0
        while not result and i in self.parse_range:
            parsekey = self.parse_order[i]
            result = self.check_key[parsekey](key, keywords)
            i += 1
        return result

    def _stable(self, key, keywords):
        """test for a normal stable keyword"""
        if key in keywords:
            return key
        return ""

    def _testing(self, key, keywords):
        """test for a normal testing keyword"""
        if ("~" + key) in keywords:
            return "~" + key
        return ""

    def _prefix(self, key, keywords):
        """test for a stable prefix keyword"""
        if not self.prefix:
            return ""
        _key = "-".join([key, self.prefix])
        if _key in keywords:
            # print key, "is in", keywords
            return _key
        return ""

    def _testing_prefix(self, key, keywords):
        """test for a testing prefix keyword"""
        if not self.prefix:
            return ""
        _key = "~" + "-".join([key, self.prefix])
        if _key in keywords:
            # print key, "is in", keywords
            return _key
        return ""

    def _missing(self, key, keywords):
        """generates a missing keyword to return"""
        if self.prefix and key != self.keyword:
            _key = "-".join([key, self.prefix])
        else:
            _key = "-" + key
        # print "_missisng :(  _key =", _key
        return _key

    def get_inst_keyword_cpv(self, cpv):
        """Determines the installed with keyword for cpv

        @type cpv: string
        @param cpv: an installed CAT/PKG-VER
        @rtype: string
        @returns a keyword determined to have been used to install cpv
        """
        keywords, used = self.vardb.aux_get(cpv, ["KEYWORDS", "USE"])
        keywords = keywords.split()
        used = used.split()
        return self._parse(keywords, used, cpv=cpv)

    def get_inst_keyword_pkg(self, pkg):
        """Determines the installed with keyword for cpv

        @param pkg: gentoolkit.package.Package object
        @rtype: string
        @returns a keyword determined to have been used to install cpv
        """
        keywords, used = pkg.environment(
            ["KEYWORDS", "USE"], prefer_vdb=True, fallback=False
        )
        keywords = keywords.split()
        used = used.split()
        return self._parse(keywords, used, pkg=pkg)

    def _parse(self, keywords, used, pkg=None, cpv=None):
        if pkg:
            _cpv = pkg.cpv
        else:
            _cpv = cpv
        if not self.parse_order:
            self.set_order(used)
        keyword = self.keyword
        # sanity check
        if self.arch not in used:
            # print "Found a mismatch = ", cpv, self.arch, used
            self.mismatched.append(_cpv)
        if keyword in keywords:
            # print "keyword", keyword, "is in", keywords
            return keyword
        elif "~" + keyword in keywords:
            # print "~keyword", keyword, "is in", keywords
            return "~" + keyword
        else:
            keyword = self.determine_keyword(keywords, used, _cpv)
            if not keyword:
                raise errors.GentoolkitUnknownKeyword(_cpv, " ".join(keywords), used)
            return keyword

    def set_order(self, used):
        """Used to set the parsing order to determine a keyword
        used for installation.

        This is needed due to the way prefix arch's and keywords
        work with portage.  It looks for the 'prefix' flag. A positive result
        sets it to the prefix order and keyword.

        @type used: list
        @param used: a list of pkg USE flags or the system USE flags"""
        if "prefix" in used:
            # print "SET_ORDER() Setting parse order to prefix"
            prefix = None
            self.parse_order = self.prefix_order
            for key in self.accept_keywords:
                # print "SET_ORDER()  '"+key+"'"
                if "-" in key:
                    # print "SET_ORDER()found prefix keyword :", key
                    if self.arch in key:
                        prefix = key.split("-")[1]
                        # print "prefix =", prefix
                        self.prefix = prefix
            self.keyword = "-".join([self.arch, prefix])
        else:
            # print "SET_ORDER() Setting parse order to normal"
            self.parse_order = self.normal_order
            self.keyword = self.arch
        # print "SET_ORDER() completed: prefix =", self.prefix, ", keyword =", \
        #   self.keyword, "parse order =",self.parse_order
        # print