Add support for the following attributes in layout.conf to allow more
[portage.git] / pym / portage / package / ebuild / _config / MaskManager.py
1 # Copyright 2010-2014 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 __all__ = (
5         'MaskManager',
6 )
7
8 import warnings
9
10 from portage import os
11 from portage.dep import ExtendedAtomDict, match_from_list
12 from portage.localization import _
13 from portage.util import append_repo, grabfile_package, stack_lists, writemsg
14 from portage.versions import _pkg_str
15
16 class MaskManager(object):
17
18         def __init__(self, repositories, profiles, abs_user_config,
19                 user_config=True, strict_umatched_removal=False):
20                 self._punmaskdict = ExtendedAtomDict(list)
21                 self._pmaskdict = ExtendedAtomDict(list)
22                 # Preserves atoms that are eliminated by negative
23                 # incrementals in user_pkgmasklines.
24                 self._pmaskdict_raw = ExtendedAtomDict(list)
25
26                 #Read profile/package.mask from every repo.
27                 #Repositories inherit masks from their parent profiles and
28                 #are able to remove mask from them with -atoms.
29                 #Such a removal affects only the current repo, but not the parent.
30                 #Add ::repo specs to every atom to make sure atoms only affect
31                 #packages from the current repo.
32
33                 # Cache the repository-wide package.mask files as a particular
34                 # repo may be often referenced by others as the master.
35                 pmask_cache = {}
36
37                 def grab_pmask(loc, repo_config):
38                         if loc not in pmask_cache:
39                                 path = os.path.join(loc, 'profiles', 'package.mask')
40                                 pmask_cache[loc] = grabfile_package(path,
41                                                 recursive=repo_config.portage1_profiles,
42                                                 remember_source_file=True, verify_eapi=True)
43                                 if repo_config.portage1_profiles_compat and os.path.isdir(path):
44                                         warnings.warn(_("Repository '%(repo_name)s' is implicitly using "
45                                                 "'portage-1' profile format in its profiles/package.mask, but "
46                                                 "the repository profiles are not marked as that format.  This will break "
47                                                 "in the future.  Please either convert the following paths "
48                                                 "to files, or add\nprofile-formats = portage-1\nto the "
49                                                 "repository's layout.conf.\n")
50                                                 % dict(repo_name=repo_config.name))
51
52                         return pmask_cache[loc]
53
54                 repo_pkgmasklines = []
55                 for repo in repositories.repos_with_profiles():
56                         lines = []
57                         repo_lines = grab_pmask(repo.location, repo)
58                         removals = frozenset(line[0][1:] for line in repo_lines
59                                 if line[0][:1] == "-")
60                         matched_removals = set()
61                         if repo.package_mask_masters is not None:
62                                 masters = repo.package_mask_masters
63                         else:
64                                 masters = repo.masters
65                         for master in masters:
66                                 master_lines = grab_pmask(master.location, master)
67                                 for line in master_lines:
68                                         if line[0] in removals:
69                                                 matched_removals.add(line[0])
70                                 # Since we don't stack masters recursively, there aren't any
71                                 # atoms earlier in the stack to be matched by negative atoms in
72                                 # master_lines. Also, repo_lines may contain negative atoms
73                                 # that are intended to negate atoms from a different master
74                                 # than the one with which we are currently stacking. Therefore,
75                                 # we disable warn_for_unmatched_removal here (see bug #386569).
76                                 lines.append(stack_lists([master_lines, repo_lines], incremental=1,
77                                         remember_source_file=True, warn_for_unmatched_removal=False))
78
79                         # It's safe to warn for unmatched removal if masters have not
80                         # been overridden by the user, which is guaranteed when
81                         # user_config is false (when called by repoman).
82                         if masters:
83                                 unmatched_removals = removals.difference(matched_removals)
84                                 if unmatched_removals and not user_config:
85                                         source_file = os.path.join(repo.location,
86                                                 "profiles", "package.mask")
87                                         unmatched_removals = list(unmatched_removals)
88                                         if len(unmatched_removals) > 3:
89                                                 writemsg(
90                                                         _("--- Unmatched removal atoms in %s: %s and %s more\n") %
91                                                         (source_file,
92                                                         ", ".join("-" + x for x in unmatched_removals[:3]),
93                                                         len(unmatched_removals) - 3), noiselevel=-1)
94                                         else:
95                                                 writemsg(
96                                                         _("--- Unmatched removal atom(s) in %s: %s\n") %
97                                                         (source_file,
98                                                         ", ".join("-" + x for x in unmatched_removals)),
99                                                         noiselevel=-1)
100
101                         else:
102                                 lines.append(stack_lists([repo_lines], incremental=1,
103                                         remember_source_file=True, warn_for_unmatched_removal=not user_config,
104                                         strict_warn_for_unmatched_removal=strict_umatched_removal))
105                         repo_pkgmasklines.extend(append_repo(stack_lists(lines), repo.name, remember_source_file=True))
106
107                 repo_pkgunmasklines = []
108                 for repo in repositories.repos_with_profiles():
109                         if not repo.portage1_profiles:
110                                 continue
111                         repo_lines = grabfile_package(os.path.join(repo.location, "profiles", "package.unmask"), \
112                                 recursive=1, remember_source_file=True, verify_eapi=True)
113                         lines = stack_lists([repo_lines], incremental=1, \
114                                 remember_source_file=True, warn_for_unmatched_removal=True,
115                                 strict_warn_for_unmatched_removal=strict_umatched_removal)
116                         repo_pkgunmasklines.extend(append_repo(lines, repo.name, remember_source_file=True))
117
118                 #Read package.mask from the user's profile. Stack them in the end
119                 #to allow profiles to override masks from their parent profiles.
120                 profile_pkgmasklines = []
121                 profile_pkgunmasklines = []
122                 for x in profiles:
123                         profile_pkgmasklines.append(grabfile_package(
124                                 os.path.join(x.location, "package.mask"),
125                                 recursive=x.portage1_directories,
126                                 remember_source_file=True, verify_eapi=True))
127                         if x.portage1_directories:
128                                 profile_pkgunmasklines.append(grabfile_package(
129                                         os.path.join(x.location, "package.unmask"),
130                                         recursive=x.portage1_directories,
131                                         remember_source_file=True, verify_eapi=True))
132                 profile_pkgmasklines = stack_lists(profile_pkgmasklines, incremental=1, \
133                         remember_source_file=True, warn_for_unmatched_removal=True,
134                         strict_warn_for_unmatched_removal=strict_umatched_removal)
135                 profile_pkgunmasklines = stack_lists(profile_pkgunmasklines, incremental=1, \
136                         remember_source_file=True, warn_for_unmatched_removal=True,
137                         strict_warn_for_unmatched_removal=strict_umatched_removal)
138
139                 #Read /etc/portage/package.mask. Don't stack it to allow the user to
140                 #remove mask atoms from everywhere with -atoms.
141                 user_pkgmasklines = []
142                 user_pkgunmasklines = []
143                 if user_config:
144                         user_pkgmasklines = grabfile_package(
145                                 os.path.join(abs_user_config, "package.mask"), recursive=1, \
146                                 allow_wildcard=True, allow_repo=True, remember_source_file=True, verify_eapi=False)
147                         user_pkgunmasklines = grabfile_package(
148                                 os.path.join(abs_user_config, "package.unmask"), recursive=1, \
149                                 allow_wildcard=True, allow_repo=True, remember_source_file=True, verify_eapi=False)
150
151                 #Stack everything together. At this point, only user_pkgmasklines may contain -atoms.
152                 #Don't warn for unmatched -atoms here, since we don't do it for any other user config file.
153                 raw_pkgmasklines = stack_lists([repo_pkgmasklines, profile_pkgmasklines], \
154                         incremental=1, remember_source_file=True, warn_for_unmatched_removal=False, ignore_repo=True)
155                 pkgmasklines = stack_lists([repo_pkgmasklines, profile_pkgmasklines, user_pkgmasklines], \
156                         incremental=1, remember_source_file=True, warn_for_unmatched_removal=False, ignore_repo=True)
157                 pkgunmasklines = stack_lists([repo_pkgunmasklines, profile_pkgunmasklines, user_pkgunmasklines], \
158                         incremental=1, remember_source_file=True, warn_for_unmatched_removal=False, ignore_repo=True)
159
160                 for x, source_file in raw_pkgmasklines:
161                         self._pmaskdict_raw.setdefault(x.cp, []).append(x)
162
163                 for x, source_file in pkgmasklines:
164                         self._pmaskdict.setdefault(x.cp, []).append(x)
165
166                 for x, source_file in pkgunmasklines:
167                         self._punmaskdict.setdefault(x.cp, []).append(x)
168
169                 for d in (self._pmaskdict_raw, self._pmaskdict, self._punmaskdict):
170                         for k, v in d.items():
171                                 d[k] = tuple(v)
172
173         def _getMaskAtom(self, cpv, slot, repo, unmask_atoms=None):
174                 """
175                 Take a package and return a matching package.mask atom, or None if no
176                 such atom exists or it has been cancelled by package.unmask. PROVIDE
177                 is not checked, so atoms will not be found for old-style virtuals.
178
179                 @param cpv: The package name
180                 @type cpv: String
181                 @param slot: The package's slot
182                 @type slot: String
183                 @param repo: The package's repository [optional]
184                 @type repo: String
185                 @param unmask_atoms: if desired pass in self._punmaskdict.get(cp)
186                 @type unmask_atoms: list
187                 @rtype: String
188                 @return: A matching atom string or None if one is not found.
189                 """
190
191                 try:
192                         cpv.slot
193                 except AttributeError:
194                         pkg = _pkg_str(cpv, slot=slot, repo=repo)
195                 else:
196                         pkg = cpv
197
198                 mask_atoms = self._pmaskdict.get(pkg.cp)
199                 if mask_atoms:
200                         pkg_list = [pkg]
201                         for x in mask_atoms:
202                                 if not match_from_list(x, pkg_list):
203                                         continue
204                                 if unmask_atoms:
205                                         for y in unmask_atoms:
206                                                 if match_from_list(y, pkg_list):
207                                                         return None
208                                 return x
209                 return None
210
211
212         def getMaskAtom(self, cpv, slot, repo):
213                 """
214                 Take a package and return a matching package.mask atom, or None if no
215                 such atom exists or it has been cancelled by package.unmask. PROVIDE
216                 is not checked, so atoms will not be found for old-style virtuals.
217
218                 @param cpv: The package name
219                 @type cpv: String
220                 @param slot: The package's slot
221                 @type slot: String
222                 @param repo: The package's repository [optional]
223                 @type repo: String
224                 @rtype: String
225                 @return: A matching atom string or None if one is not found.
226                 """
227
228                 try:
229                         cpv.slot
230                 except AttributeError:
231                         pkg = _pkg_str(cpv, slot=slot, repo=repo)
232                 else:
233                         pkg = cpv
234
235                 return self._getMaskAtom(pkg, slot, repo,
236                         self._punmaskdict.get(pkg.cp))
237
238
239         def getRawMaskAtom(self, cpv, slot, repo):
240                 """
241                 Take a package and return a matching package.mask atom, or None if no
242                 such atom exists. It HAS NOT! been cancelled by any package.unmask.
243                 PROVIDE is not checked, so atoms will not be found for old-style
244                 virtuals.
245
246                 @param cpv: The package name
247                 @type cpv: String
248                 @param slot: The package's slot
249                 @type slot: String
250                 @param repo: The package's repository [optional]
251                 @type repo: String
252                 @rtype: String
253                 @return: A matching atom string or None if one is not found.
254                 """
255
256                 return self._getMaskAtom(cpv, slot, repo)