Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] Support override of default profile EAPI (532670)
Date: Wed, 17 Dec 2014 19:40:17
Message-Id: 1418845194-24791-1-git-send-email-zmedico@gentoo.org
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

Replies