1 |
Add support for a profile-default-eapi flag in the metadata/layout.conf |
2 |
profile-formats field. When this flag is enabled, it enables support for |
3 |
a profile_eapi_when_unspecified attribute which can be used to specify |
4 |
the default EAPI to use for profile directories that do not contain an |
5 |
eapi file. For example, the following settings will cause all profiles |
6 |
that do not contain an eapi file to default to EAPI 5 instead of EAPI 0: |
7 |
|
8 |
profile-formats = profile-default-eapi |
9 |
profile_eapi_when_unspecified = 5 |
10 |
|
11 |
This is convenient for organizations that maintain their own profiles |
12 |
in separate repositories from Gentoo, since they typically want to use |
13 |
EAPI 5 for all of their profiles, and this allows them to avoid having |
14 |
separate eapi files in each directory of their profiles. The name of |
15 |
the profile_eapi_when_unspecified attribute is inherited from Paludis, |
16 |
which has supported this attribute in exheres repositories for many |
17 |
years. |
18 |
|
19 |
The default EAPI setting is stored in the RepoConfig.eapi attribute, |
20 |
which is used as the "default" parameter for all |
21 |
read_corresponding_eapi_file calls. Each profile node in |
22 |
LocationsManager.profiles_complex now has an eapi attribute which |
23 |
serves to cache the computed EAPI for that node, allowing redundant |
24 |
read_corresponding_eapi_file calls to be eliminated. As a result, |
25 |
functions such as grabfile_package now have eapi and eapi_default |
26 |
parameters, where eapi is used to pass in a cached EAPI, and |
27 |
eapi_default is used to pass in a default for |
28 |
read_corresponding_eapi_file to use when a cached EAPI is not |
29 |
available. For /etc/portage/profile, the EAPI is considered to have a |
30 |
default value of None, which means that atoms from any supported EAPI |
31 |
are allowed. |
32 |
|
33 |
X-Gentoo-Bug: 532670 |
34 |
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=532670 |
35 |
--- |
36 |
This patch superseedes the eapi-x profile-formats patch, since feedback |
37 |
from all commenters on bug #532670 expressed desires to have the default |
38 |
EAPI expressed using a separate attribute from profile-formats. |
39 |
|
40 |
man/portage.5 | 13 ++- |
41 |
pym/portage/_sets/ProfilePackageSet.py | 3 +- |
42 |
pym/portage/_sets/profiles.py | 27 +++-- |
43 |
.../package/ebuild/_config/KeywordsManager.py | 6 +- |
44 |
.../package/ebuild/_config/LocationsManager.py | 37 ++++-- |
45 |
pym/portage/package/ebuild/_config/MaskManager.py | 12 +- |
46 |
pym/portage/package/ebuild/_config/UseManager.py | 88 +++++++++++--- |
47 |
pym/portage/package/ebuild/config.py | 9 +- |
48 |
pym/portage/repository/config.py | 41 ++++++- |
49 |
.../tests/resolver/test_profile_default_eapi.py | 126 +++++++++++++++++++++ |
50 |
pym/portage/util/__init__.py | 11 +- |
51 |
11 files changed, 314 insertions(+), 59 deletions(-) |
52 |
create mode 100644 pym/portage/tests/resolver/test_profile_default_eapi.py |
53 |
|
54 |
diff --git a/man/portage.5 b/man/portage.5 |
55 |
index 88cf3bb..fbff9a6 100644 |
56 |
--- a/man/portage.5 |
57 |
+++ b/man/portage.5 |
58 |
@@ -254,6 +254,11 @@ use.stable.force, package.use.stable.mask and |
59 |
package.use.stable.force. These files behave similarly to |
60 |
previously supported USE configuration files, except that they |
61 |
only influence packages that are merged due to a stable keyword. |
62 |
+ |
63 |
+If the eapi file does not exist, then the \fBEAPI\fR defaults to |
64 |
+\fI0\fR unless the default has been overridden by a |
65 |
+profile_eapi_when_unspecified setting inside \fImetadata/layout.conf\fR |
66 |
+of the containing repository. |
67 |
.TP |
68 |
.BR make.defaults |
69 |
The profile default settings for Portage. The general format is described |
70 |
@@ -1114,7 +1119,11 @@ The default setting for repoman's --echangelog option. |
71 |
The cache formats supported in the metadata tree. There is the old "pms" format |
72 |
and the newer/faster "md5-dict" format. Default is to detect dirs. |
73 |
.TP |
74 |
-.BR profile\-formats " = [pms|portage-1|portage-2|profile-bashrcs|profile-set]" |
75 |
+.BR profile_eapi_when_unspecified |
76 |
+The EAPI to use for profiles when unspecified. This attribute is |
77 |
+supported only if profile-default-eapi is included in profile-formats. |
78 |
+.TP |
79 |
+.BR profile\-formats " = [pms] [portage-1] [portage-2] [profile-bashrcs] [profile-set] [profile-default-eapi]" |
80 |
Control functionality available to profiles in this repo such as which files |
81 |
may be dirs, or the syntax available in parent files. Use "portage-2" if you're |
82 |
unsure. The default is "portage-1-compat" mode which is meant to be compatible |
83 |
@@ -1123,6 +1132,8 @@ Setting profile-bashrcs will enable the per-profile bashrc mechanism |
84 |
\fBpackage.bashrc\fR. Setting profile-set enables support for using the |
85 |
profile \fBpackages\fR file to add atoms to the @profile package set. |
86 |
See the profile \fBpackages\fR section for more information. |
87 |
+Setting profile-default-eapi enables support for the |
88 |
+profile_eapi_when_unspecified attribute. |
89 |
.RE |
90 |
.RE |
91 |
|
92 |
diff --git a/pym/portage/_sets/ProfilePackageSet.py b/pym/portage/_sets/ProfilePackageSet.py |
93 |
index c2f5fee..2fcafb6 100644 |
94 |
--- a/pym/portage/_sets/ProfilePackageSet.py |
95 |
+++ b/pym/portage/_sets/ProfilePackageSet.py |
96 |
@@ -23,7 +23,8 @@ class ProfilePackageSet(PackageSet): |
97 |
def load(self): |
98 |
self._setAtoms(x for x in stack_lists( |
99 |
[grabfile_package(os.path.join(y.location, "packages"), |
100 |
- verify_eapi=True) for y in self._profiles |
101 |
+ verify_eapi=True, eapi=y.eapi, eapi_default=None) |
102 |
+ for y in self._profiles |
103 |
if "profile-set" in y.profile_formats], |
104 |
incremental=1) if x[:1] != "*") |
105 |
|
106 |
diff --git a/pym/portage/_sets/profiles.py b/pym/portage/_sets/profiles.py |
107 |
index 39a2968..ccb3432 100644 |
108 |
--- a/pym/portage/_sets/profiles.py |
109 |
+++ b/pym/portage/_sets/profiles.py |
110 |
@@ -1,4 +1,4 @@ |
111 |
-# Copyright 2007 Gentoo Foundation |
112 |
+# Copyright 2007-2014 Gentoo Foundation |
113 |
# Distributed under the terms of the GNU General Public License v2 |
114 |
|
115 |
import logging |
116 |
@@ -14,15 +14,15 @@ __all__ = ["PackagesSystemSet"] |
117 |
class PackagesSystemSet(PackageSet): |
118 |
_operations = ["merge"] |
119 |
|
120 |
- def __init__(self, profile_paths, debug=False): |
121 |
+ def __init__(self, profiles, debug=False): |
122 |
super(PackagesSystemSet, self).__init__() |
123 |
- self._profile_paths = profile_paths |
124 |
+ self._profiles = profiles |
125 |
self._debug = debug |
126 |
- if profile_paths: |
127 |
- description = self._profile_paths[-1] |
128 |
- if description == "/etc/portage/profile" and \ |
129 |
- len(self._profile_paths) > 1: |
130 |
- description = self._profile_paths[-2] |
131 |
+ if profiles: |
132 |
+ desc_profile = profiles[-1] |
133 |
+ if desc_profile.user_config and len(profiles) > 1: |
134 |
+ desc_profile = profiles[-2] |
135 |
+ description = desc_profile.location |
136 |
else: |
137 |
description = None |
138 |
self.description = "System packages for profile %s" % description |
139 |
@@ -30,10 +30,12 @@ class PackagesSystemSet(PackageSet): |
140 |
def load(self): |
141 |
debug = self._debug |
142 |
if debug: |
143 |
- writemsg_level("\nPackagesSystemSet: profile paths: %s\n" % \ |
144 |
- (self._profile_paths,), level=logging.DEBUG, noiselevel=-1) |
145 |
+ writemsg_level("\nPackagesSystemSet: profiles: %s\n" % |
146 |
+ (self._profiles,), level=logging.DEBUG, noiselevel=-1) |
147 |
|
148 |
- mylist = [grabfile_package(os.path.join(x, "packages"), verify_eapi=True) for x in self._profile_paths] |
149 |
+ mylist = [grabfile_package(os.path.join(x.location, "packages"), |
150 |
+ verify_eapi=True, eapi=x.eapi, eapi_default=None) |
151 |
+ for x in self._profiles] |
152 |
|
153 |
if debug: |
154 |
writemsg_level("\nPackagesSystemSet: raw packages: %s\n" % \ |
155 |
@@ -49,5 +51,6 @@ class PackagesSystemSet(PackageSet): |
156 |
|
157 |
def singleBuilder(self, options, settings, trees): |
158 |
debug = get_boolean(options, "debug", False) |
159 |
- return PackagesSystemSet(settings.profiles, debug=debug) |
160 |
+ return PackagesSystemSet( |
161 |
+ settings._locations_manager.profiles_complex, debug=debug) |
162 |
singleBuilder = classmethod(singleBuilder) |
163 |
diff --git a/pym/portage/package/ebuild/_config/KeywordsManager.py b/pym/portage/package/ebuild/_config/KeywordsManager.py |
164 |
index af606f1..e1a8e2b 100644 |
165 |
--- a/pym/portage/package/ebuild/_config/KeywordsManager.py |
166 |
+++ b/pym/portage/package/ebuild/_config/KeywordsManager.py |
167 |
@@ -1,4 +1,4 @@ |
168 |
-# Copyright 2010-2012 Gentoo Foundation |
169 |
+# Copyright 2010-2014 Gentoo Foundation |
170 |
# Distributed under the terms of the GNU General Public License v2 |
171 |
|
172 |
__all__ = ( |
173 |
@@ -22,7 +22,7 @@ class KeywordsManager(object): |
174 |
rawpkeywords = [grabdict_package( |
175 |
os.path.join(x.location, "package.keywords"), |
176 |
recursive=x.portage1_directories, |
177 |
- verify_eapi=True) \ |
178 |
+ verify_eapi=True, eapi=x.eapi, eapi_default=None) |
179 |
for x in profiles] |
180 |
for pkeyworddict in rawpkeywords: |
181 |
if not pkeyworddict: |
182 |
@@ -38,7 +38,7 @@ class KeywordsManager(object): |
183 |
raw_p_accept_keywords = [grabdict_package( |
184 |
os.path.join(x.location, "package.accept_keywords"), |
185 |
recursive=x.portage1_directories, |
186 |
- verify_eapi=True) \ |
187 |
+ verify_eapi=True, eapi=x.eapi, eapi_default=None) |
188 |
for x in profiles] |
189 |
for d in raw_p_accept_keywords: |
190 |
if not d: |
191 |
diff --git a/pym/portage/package/ebuild/_config/LocationsManager.py b/pym/portage/package/ebuild/_config/LocationsManager.py |
192 |
index 6641092..34b33e9 100644 |
193 |
--- a/pym/portage/package/ebuild/_config/LocationsManager.py |
194 |
+++ b/pym/portage/package/ebuild/_config/LocationsManager.py |
195 |
@@ -19,7 +19,7 @@ from portage.eapi import eapi_allows_directories_on_profile_level_and_repository |
196 |
from portage.exception import DirectoryNotFound, ParseError |
197 |
from portage.localization import _ |
198 |
from portage.util import ensure_dirs, grabfile, \ |
199 |
- normalize_path, shlex_split, writemsg |
200 |
+ normalize_path, read_corresponding_eapi_file, shlex_split, writemsg |
201 |
from portage.util._path import exists_raise_eaccess, isdir_raise_eaccess |
202 |
from portage.repository.config import parse_layout_conf, \ |
203 |
_portage1_profiles_allow_directories |
204 |
@@ -31,7 +31,7 @@ _PORTAGE1_DIRECTORIES = frozenset([ |
205 |
'use.mask', 'use.force']) |
206 |
|
207 |
_profile_node = collections.namedtuple('_profile_node', |
208 |
- 'location portage1_directories user_config profile_formats') |
209 |
+ 'location portage1_directories user_config profile_formats eapi') |
210 |
|
211 |
_allow_parent_colon = frozenset( |
212 |
["portage-2"]) |
213 |
@@ -71,10 +71,14 @@ class LocationsManager(object): |
214 |
known_repos = [] |
215 |
for x in known_repository_paths: |
216 |
try: |
217 |
- layout_data = {"profile-formats": |
218 |
- repositories.get_repo_for_location(x).profile_formats} |
219 |
+ repo = repositories.get_repo_for_location(x) |
220 |
except KeyError: |
221 |
layout_data = parse_layout_conf(x)[0] |
222 |
+ else: |
223 |
+ layout_data = { |
224 |
+ "profile-formats": repo.profile_formats, |
225 |
+ "profile_eapi_when_unspecified": repo.eapi |
226 |
+ } |
227 |
# force a trailing '/' for ease of doing startswith checks |
228 |
known_repos.append((x + '/', layout_data)) |
229 |
known_repos = tuple(known_repos) |
230 |
@@ -129,11 +133,16 @@ class LocationsManager(object): |
231 |
custom_prof = os.path.join( |
232 |
self.config_root, CUSTOM_PROFILE_PATH) |
233 |
if os.path.exists(custom_prof): |
234 |
+ # For read_corresponding_eapi_file, specify default=None |
235 |
+ # in order to allow things like wildcard atoms when |
236 |
+ # is no explicit EAPI setting. |
237 |
self.user_profile_dir = custom_prof |
238 |
self.profiles.append(custom_prof) |
239 |
self.profiles_complex.append( |
240 |
_profile_node(custom_prof, True, True, |
241 |
- ('profile-bashrcs', 'profile-set'))) |
242 |
+ ('profile-bashrcs', 'profile-set'), |
243 |
+ read_corresponding_eapi_file( |
244 |
+ custom_prof + os.sep, default=None))) |
245 |
del custom_prof |
246 |
|
247 |
self.profiles = tuple(self.profiles) |
248 |
@@ -153,9 +162,19 @@ class LocationsManager(object): |
249 |
repo_loc = None |
250 |
compat_mode = False |
251 |
current_formats = () |
252 |
+ eapi = None |
253 |
+ |
254 |
+ intersecting_repos = [x for x in known_repos |
255 |
+ if current_abs_path.startswith(x[0])] |
256 |
+ if intersecting_repos: |
257 |
+ # Handle nested repositories. The longest path |
258 |
+ # will be the correct one. |
259 |
+ repo_loc, layout_data = max(intersecting_repos, |
260 |
+ key=lambda x:len(x[0])) |
261 |
+ eapi = layout_data.get("profile_eapi_when_unspecified") |
262 |
|
263 |
eapi_file = os.path.join(currentPath, "eapi") |
264 |
- eapi = "0" |
265 |
+ eapi = eapi or "0" |
266 |
f = None |
267 |
try: |
268 |
f = io.open(_unicode_encode(eapi_file, |
269 |
@@ -174,11 +193,7 @@ class LocationsManager(object): |
270 |
if f is not None: |
271 |
f.close() |
272 |
|
273 |
- intersecting_repos = [x for x in known_repos if current_abs_path.startswith(x[0])] |
274 |
if intersecting_repos: |
275 |
- # protect against nested repositories. Insane configuration, but the longest |
276 |
- # path will be the correct one. |
277 |
- repo_loc, layout_data = max(intersecting_repos, key=lambda x:len(x[0])) |
278 |
allow_directories = eapi_allows_directories_on_profile_level_and_repository_level(eapi) or \ |
279 |
any(x in _portage1_profiles_allow_directories for x in layout_data['profile-formats']) |
280 |
compat_mode = not eapi_allows_directories_on_profile_level_and_repository_level(eapi) and \ |
281 |
@@ -238,7 +253,7 @@ class LocationsManager(object): |
282 |
self.profiles.append(currentPath) |
283 |
self.profiles_complex.append( |
284 |
_profile_node(currentPath, allow_directories, False, |
285 |
- current_formats)) |
286 |
+ current_formats, eapi)) |
287 |
|
288 |
def _expand_parent_colon(self, parentsFile, parentPath, |
289 |
repo_loc, repositories): |
290 |
diff --git a/pym/portage/package/ebuild/_config/MaskManager.py b/pym/portage/package/ebuild/_config/MaskManager.py |
291 |
index 0f060c9..55c8c7a 100644 |
292 |
--- a/pym/portage/package/ebuild/_config/MaskManager.py |
293 |
+++ b/pym/portage/package/ebuild/_config/MaskManager.py |
294 |
@@ -39,7 +39,8 @@ class MaskManager(object): |
295 |
path = os.path.join(loc, 'profiles', 'package.mask') |
296 |
pmask_cache[loc] = grabfile_package(path, |
297 |
recursive=repo_config.portage1_profiles, |
298 |
- remember_source_file=True, verify_eapi=True) |
299 |
+ remember_source_file=True, verify_eapi=True, |
300 |
+ eapi_default=repo_config.eapi) |
301 |
if repo_config.portage1_profiles_compat and os.path.isdir(path): |
302 |
warnings.warn(_("Repository '%(repo_name)s' is implicitly using " |
303 |
"'portage-1' profile format in its profiles/package.mask, but " |
304 |
@@ -105,7 +106,8 @@ class MaskManager(object): |
305 |
if not repo.portage1_profiles: |
306 |
continue |
307 |
repo_lines = grabfile_package(os.path.join(repo.location, "profiles", "package.unmask"), \ |
308 |
- recursive=1, remember_source_file=True, verify_eapi=True) |
309 |
+ recursive=1, remember_source_file=True, |
310 |
+ verify_eapi=True, eapi_default=repo.eapi) |
311 |
lines = stack_lists([repo_lines], incremental=1, \ |
312 |
remember_source_file=True, warn_for_unmatched_removal=True, |
313 |
strict_warn_for_unmatched_removal=strict_umatched_removal) |
314 |
@@ -119,12 +121,14 @@ class MaskManager(object): |
315 |
profile_pkgmasklines.append(grabfile_package( |
316 |
os.path.join(x.location, "package.mask"), |
317 |
recursive=x.portage1_directories, |
318 |
- remember_source_file=True, verify_eapi=True)) |
319 |
+ remember_source_file=True, verify_eapi=True, |
320 |
+ eapi=x.eapi, eapi_default=None)) |
321 |
if x.portage1_directories: |
322 |
profile_pkgunmasklines.append(grabfile_package( |
323 |
os.path.join(x.location, "package.unmask"), |
324 |
recursive=x.portage1_directories, |
325 |
- remember_source_file=True, verify_eapi=True)) |
326 |
+ remember_source_file=True, verify_eapi=True, |
327 |
+ eapi=x.eapi, eapi_default=None)) |
328 |
profile_pkgmasklines = stack_lists(profile_pkgmasklines, incremental=1, \ |
329 |
remember_source_file=True, warn_for_unmatched_removal=True, |
330 |
strict_warn_for_unmatched_removal=strict_umatched_removal) |
331 |
diff --git a/pym/portage/package/ebuild/_config/UseManager.py b/pym/portage/package/ebuild/_config/UseManager.py |
332 |
index 1c8c60e..3a4ec22 100644 |
333 |
--- a/pym/portage/package/ebuild/_config/UseManager.py |
334 |
+++ b/pym/portage/package/ebuild/_config/UseManager.py |
335 |
@@ -107,10 +107,32 @@ class UseManager(object): |
336 |
|
337 |
self.repositories = repositories |
338 |
|
339 |
- def _parse_file_to_tuple(self, file_name, recursive=True, eapi_filter=None): |
340 |
+ def _parse_file_to_tuple(self, file_name, recursive=True, |
341 |
+ eapi_filter=None, eapi=None, eapi_default="0"): |
342 |
+ """ |
343 |
+ @param file_name: input file name |
344 |
+ @type file_name: str |
345 |
+ @param recursive: triggers recursion if the input file is a |
346 |
+ directory |
347 |
+ @type recursive: bool |
348 |
+ @param eapi_filter: a function that accepts a single eapi |
349 |
+ argument, and returns true if the the current file type |
350 |
+ is supported by the given EAPI |
351 |
+ @type eapi_filter: callable |
352 |
+ @param eapi: the EAPI of the current profile node, which allows |
353 |
+ a call to read_corresponding_eapi_file to be skipped |
354 |
+ @type eapi: str |
355 |
+ @param eapi_default: the default EAPI which applies if the |
356 |
+ current profile node does not define a local EAPI |
357 |
+ @type eapi_default: str |
358 |
+ @rtype: tuple |
359 |
+ @return: collection of USE flags |
360 |
+ """ |
361 |
ret = [] |
362 |
lines = grabfile(file_name, recursive=recursive) |
363 |
- eapi = read_corresponding_eapi_file(file_name) |
364 |
+ if eapi is None: |
365 |
+ eapi = read_corresponding_eapi_file( |
366 |
+ file_name, default=eapi_default) |
367 |
if eapi_filter is not None and not eapi_filter(eapi): |
368 |
if lines: |
369 |
writemsg(_("--- EAPI '%s' does not support '%s': '%s'\n") % |
370 |
@@ -131,19 +153,46 @@ class UseManager(object): |
371 |
return tuple(ret) |
372 |
|
373 |
def _parse_file_to_dict(self, file_name, juststrings=False, recursive=True, |
374 |
- eapi_filter=None, user_config=False): |
375 |
+ eapi_filter=None, user_config=False, eapi=None, eapi_default="0"): |
376 |
+ """ |
377 |
+ @param file_name: input file name |
378 |
+ @type file_name: str |
379 |
+ @param juststrings: store dict values as space-delimited strings |
380 |
+ instead of tuples |
381 |
+ @type juststrings: bool |
382 |
+ @param recursive: triggers recursion if the input file is a |
383 |
+ directory |
384 |
+ @type recursive: bool |
385 |
+ @param eapi_filter: a function that accepts a single eapi |
386 |
+ argument, and returns true if the the current file type |
387 |
+ is supported by the given EAPI |
388 |
+ @type eapi_filter: callable |
389 |
+ @param user_config: current file is part of the local |
390 |
+ configuration (not repository content) |
391 |
+ @type user_config: bool |
392 |
+ @param eapi: the EAPI of the current profile node, which allows |
393 |
+ a call to read_corresponding_eapi_file to be skipped |
394 |
+ @type eapi: str |
395 |
+ @param eapi_default: the default EAPI which applies if the |
396 |
+ current profile node does not define a local EAPI |
397 |
+ @type eapi_default: str |
398 |
+ @rtype: tuple |
399 |
+ @return: collection of USE flags |
400 |
+ """ |
401 |
ret = {} |
402 |
location_dict = {} |
403 |
- eapi = read_corresponding_eapi_file(file_name, default=None) |
404 |
- if eapi is None and not user_config: |
405 |
- eapi = "0" |
406 |
if eapi is None: |
407 |
+ eapi = read_corresponding_eapi_file(file_name, |
408 |
+ default=eapi_default) |
409 |
+ extended_syntax = eapi is None and user_config |
410 |
+ if extended_syntax: |
411 |
ret = ExtendedAtomDict(dict) |
412 |
else: |
413 |
ret = {} |
414 |
file_dict = grabdict_package(file_name, recursive=recursive, |
415 |
- allow_wildcard=(eapi is None), allow_repo=(eapi is None), |
416 |
- verify_eapi=(eapi is not None)) |
417 |
+ allow_wildcard=extended_syntax, allow_repo=extended_syntax, |
418 |
+ verify_eapi=(not extended_syntax), eapi=eapi, |
419 |
+ eapi_default=eapi_default) |
420 |
if eapi is not None and eapi_filter is not None and not eapi_filter(eapi): |
421 |
if file_dict: |
422 |
writemsg(_("--- EAPI '%s' does not support '%s': '%s'\n") % |
423 |
@@ -185,35 +234,41 @@ class UseManager(object): |
424 |
def _parse_repository_files_to_dict_of_tuples(self, file_name, repositories, eapi_filter=None): |
425 |
ret = {} |
426 |
for repo in repositories.repos_with_profiles(): |
427 |
- ret[repo.name] = self._parse_file_to_tuple(os.path.join(repo.location, "profiles", file_name), eapi_filter=eapi_filter) |
428 |
+ ret[repo.name] = self._parse_file_to_tuple( |
429 |
+ os.path.join(repo.location, "profiles", file_name), |
430 |
+ eapi_filter=eapi_filter, eapi_default=repo.eapi) |
431 |
return ret |
432 |
|
433 |
def _parse_repository_files_to_dict_of_dicts(self, file_name, repositories, eapi_filter=None): |
434 |
ret = {} |
435 |
for repo in repositories.repos_with_profiles(): |
436 |
- ret[repo.name] = self._parse_file_to_dict(os.path.join(repo.location, "profiles", file_name), eapi_filter=eapi_filter) |
437 |
+ ret[repo.name] = self._parse_file_to_dict( |
438 |
+ os.path.join(repo.location, "profiles", file_name), |
439 |
+ eapi_filter=eapi_filter, eapi_default=repo.eapi) |
440 |
return ret |
441 |
|
442 |
def _parse_profile_files_to_tuple_of_tuples(self, file_name, locations, |
443 |
eapi_filter=None): |
444 |
return tuple(self._parse_file_to_tuple( |
445 |
os.path.join(profile.location, file_name), |
446 |
- recursive=profile.portage1_directories, eapi_filter=eapi_filter) |
447 |
- for profile in locations) |
448 |
+ recursive=profile.portage1_directories, |
449 |
+ eapi_filter=eapi_filter, eapi=profile.eapi, |
450 |
+ eapi_default=None) for profile in locations) |
451 |
|
452 |
def _parse_profile_files_to_tuple_of_dicts(self, file_name, locations, |
453 |
juststrings=False, eapi_filter=None): |
454 |
return tuple(self._parse_file_to_dict( |
455 |
os.path.join(profile.location, file_name), juststrings, |
456 |
recursive=profile.portage1_directories, eapi_filter=eapi_filter, |
457 |
- user_config=profile.user_config) |
458 |
- for profile in locations) |
459 |
+ user_config=profile.user_config, eapi=profile.eapi, |
460 |
+ eapi_default=None) for profile in locations) |
461 |
|
462 |
def _parse_repository_usealiases(self, repositories): |
463 |
ret = {} |
464 |
for repo in repositories.repos_with_profiles(): |
465 |
file_name = os.path.join(repo.location, "profiles", "use.aliases") |
466 |
- eapi = read_corresponding_eapi_file(file_name) |
467 |
+ eapi = read_corresponding_eapi_file( |
468 |
+ file_name, default=repo.eapi) |
469 |
useflag_re = _get_useflag_re(eapi) |
470 |
raw_file_dict = grabdict(file_name, recursive=True) |
471 |
file_dict = {} |
472 |
@@ -238,7 +293,8 @@ class UseManager(object): |
473 |
ret = {} |
474 |
for repo in repositories.repos_with_profiles(): |
475 |
file_name = os.path.join(repo.location, "profiles", "package.use.aliases") |
476 |
- eapi = read_corresponding_eapi_file(file_name) |
477 |
+ eapi = read_corresponding_eapi_file( |
478 |
+ file_name, default=repo.eapi) |
479 |
useflag_re = _get_useflag_re(eapi) |
480 |
lines = grabfile(file_name, recursive=True) |
481 |
file_dict = {} |
482 |
diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py |
483 |
index 65de93e..b7dd9ea 100644 |
484 |
--- a/pym/portage/package/ebuild/config.py |
485 |
+++ b/pym/portage/package/ebuild/config.py |
486 |
@@ -564,8 +564,10 @@ class config(object): |
487 |
self.user_profile_dir = locations_manager.user_profile_dir |
488 |
|
489 |
try: |
490 |
- packages_list = [grabfile_package(os.path.join(x, "packages"), |
491 |
- verify_eapi=True) for x in self.profiles] |
492 |
+ packages_list = [grabfile_package( |
493 |
+ os.path.join(x.location, "packages"), |
494 |
+ verify_eapi=True, eapi=x.eapi, eapi_default=None) |
495 |
+ for x in profiles_complex] |
496 |
except IOError as e: |
497 |
if e.errno == IsADirectory.errno: |
498 |
raise IsADirectory(os.path.join(self.profile_path, |
499 |
@@ -758,7 +760,8 @@ class config(object): |
500 |
portage.dep.ExtendedAtomDict(dict) |
501 |
bashrc = grabdict_package(os.path.join(profile.location, |
502 |
"package.bashrc"), recursive=1, allow_wildcard=True, |
503 |
- allow_repo=True, verify_eapi=False) |
504 |
+ allow_repo=True, verify_eapi=True, |
505 |
+ eapi=profile.eapi, eapi_default=None) |
506 |
if not bashrc: |
507 |
continue |
508 |
|
509 |
diff --git a/pym/portage/repository/config.py b/pym/portage/repository/config.py |
510 |
index 9096d73..ce078ed 100644 |
511 |
--- a/pym/portage/repository/config.py |
512 |
+++ b/pym/portage/repository/config.py |
513 |
@@ -41,7 +41,8 @@ if sys.hexversion >= 0x3000000: |
514 |
_invalid_path_char_re = re.compile(r'[^a-zA-Z0-9._\-+:/]') |
515 |
|
516 |
_valid_profile_formats = frozenset( |
517 |
- ['pms', 'portage-1', 'portage-2', 'profile-bashrcs', 'profile-set']) |
518 |
+ ['pms', 'portage-1', 'portage-2', 'profile-bashrcs', 'profile-set', |
519 |
+ 'profile-default-eapi']) |
520 |
|
521 |
_portage1_profiles_allow_directories = frozenset( |
522 |
["portage-1-compat", "portage-1", 'portage-2']) |
523 |
@@ -190,11 +191,9 @@ class RepoConfig(object): |
524 |
location = None |
525 |
self.location = location |
526 |
|
527 |
- eapi = None |
528 |
missing = True |
529 |
self.name = name |
530 |
if self.location is not None: |
531 |
- eapi = read_corresponding_eapi_file(os.path.join(self.location, REPO_NAME_LOC)) |
532 |
self.name, missing = self._read_valid_repo_name(self.location) |
533 |
if missing: |
534 |
# The name from repos.conf has to be used here for |
535 |
@@ -208,7 +207,7 @@ class RepoConfig(object): |
536 |
elif name == "DEFAULT": |
537 |
missing = False |
538 |
|
539 |
- self.eapi = eapi |
540 |
+ self.eapi = None |
541 |
self.missing_repo_name = missing |
542 |
# sign_commit is disabled by default, since it requires Git >=1.7.9, |
543 |
# and key_id configured by `git config user.signingkey key_id` |
544 |
@@ -258,6 +257,16 @@ class RepoConfig(object): |
545 |
'sign-commit', 'sign-manifest', 'thin-manifest', 'update-changelog'): |
546 |
setattr(self, value.lower().replace("-", "_"), layout_data[value]) |
547 |
|
548 |
+ # If profile-formats specifies a default EAPI, then set |
549 |
+ # self.eapi to that, otherwise set it to "0" as specified |
550 |
+ # by PMS. |
551 |
+ self.eapi = layout_data.get( |
552 |
+ 'profile_eapi_when_unspecified', '0') |
553 |
+ |
554 |
+ eapi = read_corresponding_eapi_file( |
555 |
+ os.path.join(self.location, REPO_NAME_LOC), |
556 |
+ default=self.eapi) |
557 |
+ |
558 |
self.portage1_profiles = eapi_allows_directories_on_profile_level_and_repository_level(eapi) or \ |
559 |
any(x in _portage1_profiles_allow_directories for x in layout_data['profile-formats']) |
560 |
self.portage1_profiles_compat = not eapi_allows_directories_on_profile_level_and_repository_level(eapi) and \ |
561 |
@@ -1085,4 +1094,28 @@ def parse_layout_conf(repo_location, repo_name=None): |
562 |
raw_formats = tuple(raw_formats.intersection(_valid_profile_formats)) |
563 |
data['profile-formats'] = raw_formats |
564 |
|
565 |
+ try: |
566 |
+ eapi = layout_data['profile_eapi_when_unspecified'] |
567 |
+ except KeyError: |
568 |
+ pass |
569 |
+ else: |
570 |
+ if 'profile-default-eapi' not in raw_formats: |
571 |
+ warnings.warn((_("Repository named '%(repo_name)s' has " |
572 |
+ "profile_eapi_when_unspecified setting in " |
573 |
+ "'%(layout_filename)s', but 'profile-default-eapi' is " |
574 |
+ "not listed in the profile-formats field. Please " |
575 |
+ "report this issue to the repository maintainer.") % |
576 |
+ dict(repo_name=repo_name or 'unspecified', |
577 |
+ layout_filename=layout_filename)), |
578 |
+ SyntaxWarning) |
579 |
+ elif not portage.eapi_is_supported(eapi): |
580 |
+ warnings.warn((_("Repository named '%(repo_name)s' has " |
581 |
+ "unsupported EAPI '%(repo_name)s' setting in " |
582 |
+ "'%(layout_filename)s'; please upgrade portage.") % |
583 |
+ dict(repo_name=repo_name or 'unspecified', |
584 |
+ layout_filename=layout_filename)), |
585 |
+ SyntaxWarning) |
586 |
+ else: |
587 |
+ data['profile_eapi_when_unspecified'] = eapi |
588 |
+ |
589 |
return data, layout_errors |
590 |
diff --git a/pym/portage/tests/resolver/test_profile_default_eapi.py b/pym/portage/tests/resolver/test_profile_default_eapi.py |
591 |
new file mode 100644 |
592 |
index 0000000..cc57219 |
593 |
--- /dev/null |
594 |
+++ b/pym/portage/tests/resolver/test_profile_default_eapi.py |
595 |
@@ -0,0 +1,126 @@ |
596 |
+# Copyright 2014 Gentoo Foundation |
597 |
+# Distributed under the terms of the GNU General Public License v2 |
598 |
+ |
599 |
+from __future__ import unicode_literals |
600 |
+ |
601 |
+import io |
602 |
+ |
603 |
+from portage import os, _encodings |
604 |
+from portage.const import USER_CONFIG_PATH |
605 |
+from portage.tests import TestCase |
606 |
+from portage.tests.resolver.ResolverPlayground import ResolverPlayground |
607 |
+from portage.dep import ExtendedAtomDict |
608 |
+from portage.util import ensure_dirs |
609 |
+ |
610 |
+class ProfileDefaultEAPITestCase(TestCase): |
611 |
+ |
612 |
+ def testProfileDefaultEAPI(self): |
613 |
+ |
614 |
+ repo_configs = { |
615 |
+ "test_repo": { |
616 |
+ "layout.conf": ( |
617 |
+ "profile-formats = profile-default-eapi", |
618 |
+ "profile_eapi_when_unspecified = 5" |
619 |
+ ), |
620 |
+ } |
621 |
+ } |
622 |
+ |
623 |
+ profiles = ( |
624 |
+ ( |
625 |
+ "", |
626 |
+ { |
627 |
+ "package.mask": ("sys-libs/A:1",), |
628 |
+ "package.use": ("sys-libs/A:1 flag",) |
629 |
+ } |
630 |
+ ), |
631 |
+ ( |
632 |
+ "default/linux", |
633 |
+ { |
634 |
+ "package.mask": ("sys-libs/B:1",), |
635 |
+ "package.use": ("sys-libs/B:1 flag",), |
636 |
+ "package.keywords": ("sys-libs/B:1 x86",) |
637 |
+ } |
638 |
+ ), |
639 |
+ ( |
640 |
+ "default/linux/x86", |
641 |
+ { |
642 |
+ "package.mask": ("sys-libs/C:1",), |
643 |
+ "package.use": ("sys-libs/C:1 flag",), |
644 |
+ "package.keywords": ("sys-libs/C:1 x86",), |
645 |
+ "parent": ("..",) |
646 |
+ } |
647 |
+ ), |
648 |
+ ) |
649 |
+ |
650 |
+ user_profile = { |
651 |
+ "package.mask": ("sys-libs/D:1",), |
652 |
+ "package.use": ("sys-libs/D:1 flag",), |
653 |
+ "package.keywords": ("sys-libs/D:1 x86",), |
654 |
+ } |
655 |
+ |
656 |
+ test_cases = ( |
657 |
+ (lambda x: x._mask_manager._pmaskdict, { |
658 |
+ "sys-libs/A": ("sys-libs/A:1::test_repo",), |
659 |
+ "sys-libs/B": ("sys-libs/B:1",), |
660 |
+ "sys-libs/C": ("sys-libs/C:1",), |
661 |
+ "sys-libs/D": ("sys-libs/D:1",), |
662 |
+ }), |
663 |
+ (lambda x: x._use_manager._repo_puse_dict, { |
664 |
+ "test_repo": { |
665 |
+ "sys-libs/A": { |
666 |
+ "sys-libs/A:1": ("flag",) |
667 |
+ } |
668 |
+ } |
669 |
+ }), |
670 |
+ (lambda x: x._use_manager._pkgprofileuse, ( |
671 |
+ {"sys-libs/B": {"sys-libs/B:1": "flag"}}, |
672 |
+ {"sys-libs/C": {"sys-libs/C:1": "flag"}}, |
673 |
+ {}, |
674 |
+ {"sys-libs/D": {"sys-libs/D:1": "flag"}}, |
675 |
+ )), |
676 |
+ (lambda x: x._keywords_manager._pkeywords_list, ( |
677 |
+ {"sys-libs/B": {"sys-libs/B:1": ["x86"]}}, |
678 |
+ {"sys-libs/C": {"sys-libs/C:1": ["x86"]}}, |
679 |
+ {"sys-libs/D": {"sys-libs/D:1": ["x86"]}}, |
680 |
+ ) |
681 |
+ ) |
682 |
+ ) |
683 |
+ |
684 |
+ playground = ResolverPlayground(debug=False, |
685 |
+ repo_configs=repo_configs) |
686 |
+ try: |
687 |
+ repo_dir = (playground.settings.repositories. |
688 |
+ get_location_for_name("test_repo")) |
689 |
+ profile_root = os.path.join(repo_dir, "profiles") |
690 |
+ profile_info = [(os.path.join(profile_root, p), data) |
691 |
+ for p, data in profiles] |
692 |
+ profile_info.append((os.path.join(playground.eroot, |
693 |
+ USER_CONFIG_PATH, "profile"), user_profile)) |
694 |
+ |
695 |
+ for prof_path, data in profile_info: |
696 |
+ ensure_dirs(prof_path) |
697 |
+ for k, v in data.items(): |
698 |
+ with io.open(os.path.join(prof_path, k), mode="w", |
699 |
+ encoding=_encodings["repo.content"]) as f: |
700 |
+ for line in v: |
701 |
+ f.write("%s\n" % line) |
702 |
+ |
703 |
+ # The config must be reloaded in order to account |
704 |
+ # for the above profile customizations. |
705 |
+ playground.reload_config() |
706 |
+ |
707 |
+ for fn, expected in test_cases: |
708 |
+ result = self._translate_result(fn(playground.settings)) |
709 |
+ self.assertEqual(result, expected) |
710 |
+ |
711 |
+ finally: |
712 |
+ playground.cleanup() |
713 |
+ |
714 |
+ |
715 |
+ @staticmethod |
716 |
+ def _translate_result(result): |
717 |
+ if isinstance(result, ExtendedAtomDict): |
718 |
+ result = dict(result.items()) |
719 |
+ elif isinstance(result, tuple): |
720 |
+ result = tuple(dict(x.items()) for x in result) |
721 |
+ return result |
722 |
diff --git a/pym/portage/util/__init__.py b/pym/portage/util/__init__.py |
723 |
index d0cca5b..61fe787 100644 |
724 |
--- a/pym/portage/util/__init__.py |
725 |
+++ b/pym/portage/util/__init__.py |
726 |
@@ -425,7 +425,7 @@ def read_corresponding_eapi_file(filename, default="0"): |
727 |
return eapi |
728 |
|
729 |
def grabdict_package(myfilename, juststrings=0, recursive=0, allow_wildcard=False, allow_repo=False, |
730 |
- verify_eapi=False, eapi=None): |
731 |
+ verify_eapi=False, eapi=None, eapi_default="0"): |
732 |
""" Does the same thing as grabdict except it validates keys |
733 |
with isvalidatom()""" |
734 |
|
735 |
@@ -441,7 +441,8 @@ def grabdict_package(myfilename, juststrings=0, recursive=0, allow_wildcard=Fals |
736 |
if not d: |
737 |
continue |
738 |
if verify_eapi and eapi is None: |
739 |
- eapi = read_corresponding_eapi_file(myfilename) |
740 |
+ eapi = read_corresponding_eapi_file( |
741 |
+ myfilename, default=eapi_default) |
742 |
|
743 |
for k, v in d.items(): |
744 |
try: |
745 |
@@ -460,13 +461,15 @@ def grabdict_package(myfilename, juststrings=0, recursive=0, allow_wildcard=Fals |
746 |
return atoms |
747 |
|
748 |
def grabfile_package(myfilename, compatlevel=0, recursive=0, allow_wildcard=False, allow_repo=False, |
749 |
- remember_source_file=False, verify_eapi=False, eapi=None): |
750 |
+ remember_source_file=False, verify_eapi=False, eapi=None, |
751 |
+ eapi_default="0"): |
752 |
|
753 |
pkgs=grabfile(myfilename, compatlevel, recursive=recursive, remember_source_file=True) |
754 |
if not pkgs: |
755 |
return pkgs |
756 |
if verify_eapi and eapi is None: |
757 |
- eapi = read_corresponding_eapi_file(myfilename) |
758 |
+ eapi = read_corresponding_eapi_file( |
759 |
+ myfilename, default=eapi_default) |
760 |
mybasename = os.path.basename(myfilename) |
761 |
atoms = [] |
762 |
for pkg, source_file in pkgs: |
763 |
-- |
764 |
2.0.4 |