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

Replies