1 # Copyright 2010-2014 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
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
16 class MaskManager(object):
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)
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.
33 # Cache the repository-wide package.mask files as a particular
34 # repo may be often referenced by others as the master.
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))
52 return pmask_cache[loc]
54 repo_pkgmasklines = []
55 for repo in repositories.repos_with_profiles():
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
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))
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).
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:
90 _("--- Unmatched removal atoms in %s: %s and %s more\n") %
92 ", ".join("-" + x for x in unmatched_removals[:3]),
93 len(unmatched_removals) - 3), noiselevel=-1)
96 _("--- Unmatched removal atom(s) in %s: %s\n") %
98 ", ".join("-" + x for x in unmatched_removals)),
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))
107 repo_pkgunmasklines = []
108 for repo in repositories.repos_with_profiles():
109 if not repo.portage1_profiles:
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))
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 = []
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)
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 = []
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)
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)
160 for x, source_file in raw_pkgmasklines:
161 self._pmaskdict_raw.setdefault(x.cp, []).append(x)
163 for x, source_file in pkgmasklines:
164 self._pmaskdict.setdefault(x.cp, []).append(x)
166 for x, source_file in pkgunmasklines:
167 self._punmaskdict.setdefault(x.cp, []).append(x)
169 for d in (self._pmaskdict_raw, self._pmaskdict, self._punmaskdict):
170 for k, v in d.items():
173 def _getMaskAtom(self, cpv, slot, repo, unmask_atoms=None):
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.
179 @param cpv: The package name
181 @param slot: The package's slot
183 @param repo: The package's repository [optional]
185 @param unmask_atoms: if desired pass in self._punmaskdict.get(cp)
186 @type unmask_atoms: list
188 @return: A matching atom string or None if one is not found.
193 except AttributeError:
194 pkg = _pkg_str(cpv, slot=slot, repo=repo)
198 mask_atoms = self._pmaskdict.get(pkg.cp)
202 if not match_from_list(x, pkg_list):
205 for y in unmask_atoms:
206 if match_from_list(y, pkg_list):
212 def getMaskAtom(self, cpv, slot, repo):
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.
218 @param cpv: The package name
220 @param slot: The package's slot
222 @param repo: The package's repository [optional]
225 @return: A matching atom string or None if one is not found.
230 except AttributeError:
231 pkg = _pkg_str(cpv, slot=slot, repo=repo)
235 return self._getMaskAtom(pkg, slot, repo,
236 self._punmaskdict.get(pkg.cp))
239 def getRawMaskAtom(self, cpv, slot, repo):
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
246 @param cpv: The package name
248 @param slot: The package's slot
250 @param repo: The package's repository [optional]
253 @return: A matching atom string or None if one is not found.
256 return self._getMaskAtom(cpv, slot, repo)