commit: d284813525a9547a4132cb7b380cfacb3e08226a Author: Zac Medico <zmedico <AT> gentoo <DOT> org> AuthorDate: Sat Nov 15 15:46:52 2025 +0000 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> CommitDate: Sat Nov 15 15:46:52 2025 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=d2848135
Implement use.stable and package.use.stable for EAPI 9 Since USE_ORDER has a repo component, also support repo-level use.stable and package.use.stable. Bug: https://bugs.gentoo.org/965968 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> lib/portage/eapi.py | 7 ++ lib/portage/package/ebuild/_config/UseManager.py | 28 +++++++- lib/portage/package/ebuild/config.py | 32 ++++++++++ lib/portage/tests/resolver/ResolverPlayground.py | 2 + lib/portage/tests/resolver/meson.build | 1 + .../tests/resolver/test_profile_use_stable.py | 74 ++++++++++++++++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) diff --git a/lib/portage/eapi.py b/lib/portage/eapi.py index 92376b5777..e980c9bf99 100644 --- a/lib/portage/eapi.py +++ b/lib/portage/eapi.py @@ -112,6 +112,10 @@ def eapi_has_repo_deps(eapi: str) -> bool: return _get_eapi_attrs(eapi).repo_deps +def eapi_supports_use_stable(eapi: str) -> bool: + return _get_eapi_attrs(eapi).use_stable + + def eapi_supports_stable_use_forcing_and_masking(eapi: str) -> bool: return _get_eapi_attrs(eapi).stablemask @@ -191,6 +195,7 @@ _eapi_attrs = collections.namedtuple( "src_prepare_src_configure", "src_uri_arrows", "stablemask", + "use_stable", "strong_blocks", "symlink_rewrite", "sysroot", @@ -273,6 +278,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs: src_prepare_src_configure=True, src_uri_arrows=True, stablemask=True, + use_stable=True, strong_blocks=True, symlink_rewrite=False, sysroot=True, @@ -314,6 +320,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs: src_prepare_src_configure=eapi >= Eapi("2"), src_uri_arrows=eapi >= Eapi("2"), stablemask=eapi >= Eapi("5"), + use_stable=eapi >= Eapi("9"), strong_blocks=eapi >= Eapi("2"), symlink_rewrite=eapi <= Eapi("8"), sysroot=eapi >= Eapi("7"), diff --git a/lib/portage/package/ebuild/_config/UseManager.py b/lib/portage/package/ebuild/_config/UseManager.py index 3827ba27a7..1da54f4f0c 100644 --- a/lib/portage/package/ebuild/_config/UseManager.py +++ b/lib/portage/package/ebuild/_config/UseManager.py @@ -1,4 +1,4 @@ -# Copyright 2010-2021 Gentoo Authors +# Copyright 2010-2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 __all__ = ("UseManager",) @@ -13,6 +13,7 @@ from portage.dep import ( _get_useflag_re, ) from portage.eapi import ( + eapi_supports_use_stable, eapi_supports_stable_use_forcing_and_masking, ) from portage.localization import _ @@ -38,10 +39,12 @@ class UseManager: # repositories # -------------------------------- # use.mask _repo_usemask_dict + # use.stable _repo_use_stable_dict # use.stable.mask _repo_usestablemask_dict # use.force _repo_useforce_dict # use.stable.force _repo_usestableforce_dict # package.use.mask _repo_pusemask_dict + # package.use.stable _repo_puse_stable_dict # package.use.stable.mask _repo_pusestablemask_dict # package.use.force _repo_puseforce_dict # package.use.stable.force _repo_pusestableforce_dict @@ -49,10 +52,12 @@ class UseManager: # profiles # -------------------------------- # use.mask _usemask_list + # use.stable _use_stable_list # use.stable.mask _usestablemask_list # use.force _useforce_list # use.stable.force _usestableforce_list # package.use.mask _pusemask_list + # package.use.stable _puse_stable_list # package.use.stable.mask _pusestablemask_list # package.use _pkgprofileuse # package.use.force _puseforce_list @@ -78,6 +83,11 @@ class UseManager: self._repo_usemask_dict = self._parse_repository_files_to_dict_of_tuples( "use.mask", repositories ) + self._repo_use_stable_dict = self._parse_repository_files_to_dict_of_tuples( + "use.stable", + repositories, + eapi_filter=eapi_supports_use_stable, + ) self._repo_usestablemask_dict = self._parse_repository_files_to_dict_of_tuples( "use.stable.mask", repositories, @@ -94,6 +104,11 @@ class UseManager: self._repo_pusemask_dict = self._parse_repository_files_to_dict_of_dicts( "package.use.mask", repositories ) + self._repo_puse_stable_dict = self._parse_repository_files_to_dict_of_dicts( + "package.use.stable", + repositories, + eapi_filter=eapi_supports_use_stable, + ) self._repo_pusestablemask_dict = self._parse_repository_files_to_dict_of_dicts( "package.use.stable.mask", repositories, @@ -114,6 +129,11 @@ class UseManager: self._usemask_list = self._parse_profile_files_to_tuple_of_tuples( "use.mask", profiles ) + self._use_stable_list = self._parse_profile_files_to_tuple_of_tuples( + "use.stable", + profiles, + eapi_filter=eapi_supports_use_stable, + ) self._usestablemask_list = self._parse_profile_files_to_tuple_of_tuples( "use.stable.mask", profiles, @@ -130,6 +150,12 @@ class UseManager: self._pusemask_list = self._parse_profile_files_to_tuple_of_dicts( "package.use.mask", profiles ) + self._puse_stable_list = self._parse_profile_files_to_tuple_of_dicts( + "package.use.stable", + profiles, + eapi_filter=eapi_supports_use_stable, + juststrings=True, + ) self._pusestablemask_list = self._parse_profile_files_to_tuple_of_dicts( "package.use.stable.mask", profiles, diff --git a/lib/portage/package/ebuild/config.py b/lib/portage/package/ebuild/config.py index 77485fac85..06adb006ff 100644 --- a/lib/portage/package/ebuild/config.py +++ b/lib/portage/package/ebuild/config.py @@ -1898,6 +1898,7 @@ class config: pkginternaluse_list.append(x) pkginternaluse = " ".join(pkginternaluse_list) + stable = self._isStable(cpv_slot) if hasattr(cpv_slot, "_metadata") else None eapi_attrs = _get_eapi_attrs(eapi) if pkginternaluse != self.configdict["pkginternal"].get("USE", ""): @@ -1922,12 +1923,30 @@ class config: # make a copy, since we might modify it with # package.use settings d = d.copy() + + # package.use.stable > package.use > use.stable + if stable: + x = self._use_manager._repo_use_stable_dict.get(repo, ()) + if x: + d["USE"] = d.get("USE", "") + " " + " ".join(x) + cpdict = self._use_manager._repo_puse_dict.get(repo, {}).get(cp) if cpdict: repo_puse = ordered_by_atom_specificity(cpdict, cpv_slot) if repo_puse: for x in repo_puse: d["USE"] = d.get("USE", "") + " " + " ".join(x) + + if stable: + cpdict = self._use_manager._repo_puse_stable_dict.get(repo, {}).get( + cp + ) + if cpdict: + repo_puse = ordered_by_atom_specificity(cpdict, cpv_slot) + if repo_puse: + for x in repo_puse: + d["USE"] = d.get("USE", "") + " " + " ".join(x) + if d: repo_env.append(d) @@ -1942,11 +1961,24 @@ class config: for i, pkgprofileuse_dict in enumerate(self._use_manager._pkgprofileuse): if self.make_defaults_use[i]: defaults.append(self.make_defaults_use[i]) + + # package.use.stable > package.use > use.stable + if stable and self._use_manager._use_stable_list[i]: + defaults.append(" ".join(self._use_manager._use_stable_list[i])) + cpdict = pkgprofileuse_dict.get(cp) if cpdict: pkg_defaults = ordered_by_atom_specificity(cpdict, cpv_slot) if pkg_defaults: defaults.extend(pkg_defaults) + + if stable: + cpdict = self._use_manager._puse_stable_list[i].get(cp) + if cpdict: + pkg_defaults = ordered_by_atom_specificity(cpdict, cpv_slot) + if pkg_defaults: + defaults.extend(pkg_defaults) + defaults = " ".join(defaults) if defaults != self.configdict["defaults"].get("USE", ""): self.configdict["defaults"]["USE"] = defaults diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py index 3e2cc6ec70..9d4aa25acc 100644 --- a/lib/portage/tests/resolver/ResolverPlayground.py +++ b/lib/portage/tests/resolver/ResolverPlayground.py @@ -65,11 +65,13 @@ class ResolverPlayground: "package.use", "package.use.force", "package.use.mask", + "package.use.stable", "package.use.stable.force", "package.use.stable.mask", "soname.provided", "use.force", "use.mask", + "use.stable", "layout.conf", ) ) diff --git a/lib/portage/tests/resolver/meson.build b/lib/portage/tests/resolver/meson.build index 653b0536e1..df7944259b 100644 --- a/lib/portage/tests/resolver/meson.build +++ b/lib/portage/tests/resolver/meson.build @@ -51,6 +51,7 @@ py.install_sources( 'test_perl_rebuild_bug.py', 'test_profile_default_eapi.py', 'test_profile_package_set.py', + 'test_profile_use_stable.py', 'test_rebuild.py', 'test_rebuild_ghostscript.py', 'test_regular_slot_change_without_revbump.py', diff --git a/lib/portage/tests/resolver/test_profile_use_stable.py b/lib/portage/tests/resolver/test_profile_use_stable.py new file mode 100644 index 0000000000..0e38fbacc2 --- /dev/null +++ b/lib/portage/tests/resolver/test_profile_use_stable.py @@ -0,0 +1,74 @@ +# Copyright 2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +from portage.tests import TestCase +from portage.tests.resolver.ResolverPlayground import ( + ResolverPlayground, + ResolverPlaygroundTestCase, +) + + +class ProfileUseStableTestCase(TestCase): + def testProfileUseStable(self): + profile = { + "eapi": ("9-pre1",), + "package.use": ("app-misc/A -a d",), + "package.use.stable": ("app-misc/A a -b -c f",), + "use.stable": ("a", "b", "c", "e"), + } + + user_config = { + "package.accept_keywords": ("=app-misc/A-2 ~x86",), + } + + ebuilds = { + "app-misc/A-1": {"EAPI": "8", "KEYWORDS": "x86", "IUSE": "a b c d e f"}, + "app-misc/A-2": {"EAPI": "8", "KEYWORDS": "~x86", "IUSE": "a b c d e f"}, + "app-misc/B-1": { + "EAPI": "8", + # package.use.stable > package.use > use.stable + "RDEPEND": "=app-misc/A-1[a,-b,-c,d,e,f]", + }, + "app-misc/C-1": { + "EAPI": "8", + # package.use.stable and use.stable do not apply due to unstable keyword + "RDEPEND": "=app-misc/A-2[-a,-b,-c,d,-e,-f]", + }, + } + + test_cases = ( + # Test stable package + ResolverPlaygroundTestCase( + ["app-misc/B"], + success=True, + mergelist=[ + "app-misc/A-1", + "app-misc/B-1", + ], + ), + # Test unstable package + ResolverPlaygroundTestCase( + ["app-misc/C"], + success=True, + mergelist=[ + "app-misc/A-2", + "app-misc/C-1", + ], + ), + ) + + playground = ResolverPlayground( + debug=False, + ebuilds=ebuilds, + profile=profile, + user_config=user_config, + ) + + try: + for test_case in test_cases: + playground.run_TestCase(test_case) + self.assertEqual(test_case.test_success, True, test_case.fail_msg) + finally: + # Disable debug so that cleanup works. + playground.debug = False + playground.cleanup()
