commit: 75a4f2c2e07c128fef4d9faf3a8fb9d67565239e Author: Sam James <sam <AT> gentoo <DOT> org> AuthorDate: Thu Feb 16 06:26:07 2023 +0000 Commit: Sam James <sam <AT> gentoo <DOT> org> CommitDate: Tue Feb 21 07:16:27 2023 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=75a4f2c2
emerge: add --update-if-installed This adds a new emerge option '--update-if-installed'. The use case for such an option is as follows: - User finds out libfoo-1.2 is buggy. - They want to upgrade all their systems if libfoo is installed. - They don't want to install libfoo if it's not already installed. Unfortunately, --update fails this last point, hence the need for a new option. Closes: https://github.com/gentoo/portage/pull/988 Signed-off-by: Sam James <sam <AT> gentoo.org> NEWS | 4 ++ lib/_emerge/create_depgraph_params.py | 6 ++ lib/_emerge/depgraph.py | 17 ++++- lib/_emerge/main.py | 3 +- lib/portage/tests/resolver/test_update.py | 106 ++++++++++++++++++++++++++++++ man/emerge.1 | 6 ++ 6 files changed, 140 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 29e9de038..9389d2c09 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ Features: * emerge: add --onlydeps-with-ideps=<y|n> option (bug #890777) +* emerge: add --update-if-installed option. This is useful for one-shot + emerge commands to be run across several machines to upgrade packages + only if they're installed. + * install-qa-check.d: 60pkgconfig: add opt-in QA_PKGCONFIG_VERSION check * emerge: Log completion of package installs. diff --git a/lib/_emerge/create_depgraph_params.py b/lib/_emerge/create_depgraph_params.py index 531230402..1bbca5de9 100644 --- a/lib/_emerge/create_depgraph_params.py +++ b/lib/_emerge/create_depgraph_params.py @@ -129,6 +129,12 @@ def create_depgraph_params(myopts, myaction): if changed_slot: myparams["changed_slot"] = True + # --update-if-installed implies --update + update_if_installed = myopts.get("--update-if-installed") + if update_if_installed is not None: + myparams["update_if_installed"] = update_if_installed + myopts["--update"] = True + if ( "--update" in myopts or "--newrepo" in myopts diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py index 1631ed126..412dc7b6f 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -1,4 +1,4 @@ -# Copyright 1999-2021 Gentoo Authors +# Copyright 1999-2023 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import errno @@ -5019,6 +5019,21 @@ class depgraph: pkg, existing_node = self._select_package( myroot, atom, onlydeps=onlydeps ) + + # Is the package installed (at any version)? + if pkg and "update_if_installed" in self._dynamic_config.myparams: + package_is_installed = any( + self._iter_match_pkgs( + self._frozen_config.roots[myroot], "installed", atom + ) + ) + + # This package isn't eligible for selection in the + # merge list as the user passed --update-if-installed + # and it isn't installed. + if not package_is_installed: + continue + if not pkg: pprovided_match = False for virt_choice in virtuals.get(atom.cp, []): diff --git a/lib/_emerge/main.py b/lib/_emerge/main.py index 38233e05c..850487d36 100644 --- a/lib/_emerge/main.py +++ b/lib/_emerge/main.py @@ -1,4 +1,4 @@ -# Copyright 1999-2020 Gentoo Authors +# Copyright 1999-2023 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import argparse @@ -52,6 +52,7 @@ options = [ "--tree", "--unordered-display", "--update", + "--update-if-installed", ] shortmapping = { diff --git a/lib/portage/tests/resolver/test_update.py b/lib/portage/tests/resolver/test_update.py new file mode 100644 index 000000000..e67013f9f --- /dev/null +++ b/lib/portage/tests/resolver/test_update.py @@ -0,0 +1,106 @@ +# Copyright 2022-2023 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 UpdateIfInstalledTestCase(TestCase): + def testUpdateIfInstalledEmerge(self): + installed = { + "dev-lang/ghc-4": {}, + "dev-libs/larryware-3": {}, + "dev-libs/larryware-ng-3": {}, + "virtual/libc-1": {}, + } + + ebuilds = installed.copy() + ebuilds.update( + { + "app-misc/cowsay-10": {}, + "dev-lang/ghc-5": {}, + "dev-libs/larryware-4": {}, + "dev-libs/larryware-ng-4": {"RDEPEND": ">=net-libs/moo-1"}, + "net-libs/moo-1": {}, + } + ) + + playground = ResolverPlayground( + ebuilds=ebuilds, installed=installed, debug=False + ) + + test_cases = ( + # We should only try to update ghc when passed ghc and + # --update-if-installed. We don't want larryware to appear here, + # despite it being eligible for an upgrade otherwise with --update. + ResolverPlaygroundTestCase( + ["dev-lang/ghc"], + mergelist=["dev-lang/ghc-5"], + options={ + "--update-if-installed": True, + }, + success=True, + ), + # Only try to upgrade ghc even if passed another candidate, + # as there's no upgrade due for it. We don't want to + # reinstall virtual/libc for the sake of it. + ResolverPlaygroundTestCase( + ["dev-lang/ghc", "virtual/libc"], + mergelist=["dev-lang/ghc-5"], + options={ + "--update-if-installed": True, + }, + success=True, + ), + # Try to upgrade a package with no new versions available. + # This is just checking we still have --update semantics. + ResolverPlaygroundTestCase( + ["virtual/libc"], + mergelist=[], + options={ + "--update-if-installed": True, + }, + success=True, + ), + # If a new package is given, we want to do nothing. + ResolverPlaygroundTestCase( + ["app-misc/cowsay"], + mergelist=[], + options={ + "--update-if-installed": True, + }, + success=True, + ), + # If a new package (app-misc/cowsay) is given combined with + # a package eligible for an upgrade (dev-libs/larryware), + # upgrade just the latter. + ResolverPlaygroundTestCase( + ["app-misc/cowsay", "dev-libs/larryware"], + mergelist=["dev-libs/larryware-4"], + options={ + "--update-if-installed": True, + }, + success=True, + ), + # Make sure that we can still pull in upgrades as + # dependencies (net-libs/moo) of the package we requested + # (dev-libs/larryware-ng). + ResolverPlaygroundTestCase( + ["dev-libs/larryware-ng"], + mergelist=["net-libs/moo-1", "dev-libs/larryware-ng-4"], + options={ + "--update-if-installed": True, + }, + success=True, + ), + ) + + try: + for test_case in test_cases: + playground.run_TestCase(test_case) + self.assertEqual(test_case.test_success, True, test_case.fail_msg) + finally: + playground.cleanup() diff --git a/man/emerge.1 b/man/emerge.1 index c85ffd7b6..a1c5f33be 100644 --- a/man/emerge.1 +++ b/man/emerge.1 @@ -1054,6 +1054,12 @@ the command line are greedy, meaning that unspecific atoms may match multiple versions of slotted packages. This option also implies the \fB\-\-selective\fR option. .TP +.BR \-\-update\-if\-installed +Acts similar to \fB\-\-update\fR except it updates packages +passed as arguments to the best version available only if they are +already installed. This is useful for oneshot commands across +a series of systems to upgrade away from a buggy version. +.TP .BR "\-\-use\-ebuild\-visibility [ y | n ]" Use unbuilt ebuild metadata for visibility checks on built packages.