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