Gentoo Archives: gentoo-commits

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