commit:     92ff02b9189f8350f44e134d538319e4037f3f71
Author:     Gábor Oszkár Dénes <gaboroszkar <AT> protonmail <DOT> com>
AuthorDate: Wed Feb 14 17:40:24 2024 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Fri Feb 23 04:39:26 2024 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=92ff02b9

emerge: Skip installed packages with emptytree in depgraph selection

Running emerge with emptytree tries to find the best match for every
atom it needs to install. Sometimes the best matches would be
already installed packages (with `operation=nomerge`), but these
packages would be silently skipped with full emptytree installation.
This change makes sure that emerge attempts to install every package.
If the package has unmet requirements, emerge will complain.

Bug: https://bugs.gentoo.org/651018
Signed-off-by: Gábor Oszkár Dénes <gaboroszkar <AT> protonmail.com>
Closes: https://github.com/gentoo/portage/pull/1272
Signed-off-by: Sam James <sam <AT> gentoo.org>

 NEWS                                               |   6 +
 lib/_emerge/depgraph.py                            |  13 ++
 lib/portage/tests/resolver/test_depth.py           |   8 +-
 .../test_emptytree_reinstall_unsatisfiability.py   | 137 +++++++++++++++++++++
 lib/portage/tests/resolver/test_useflags.py        |   6 +-
 5 files changed, 166 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 3fbc727861..94be26de84 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,12 @@ Release notes take the form of the following optional 
categories:
 * Bug fixes
 * Cleanups
 
+portage-3.0.63 (UNRELEASED)
+--------------
+
+Bug fixes:
+* emerge: Skip installed packages with emptytree in depgraph selection (bug 
#651018).
+
 portage-3.0.62 (2024-02-22)
 --------------
 

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 70b83ee1f4..ea96bd58c4 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -7639,6 +7639,19 @@ class depgraph:
                     if pkg.installed and root_slot in 
self._rebuild.reinstall_list:
                         continue
 
+                    if (
+                        empty
+                        and pkg.installed
+                        and not 
self._frozen_config.excluded_pkgs.findAtomForPackage(
+                            pkg, modified_use=self._pkg_use_enabled(pkg)
+                        )
+                    ):
+                        # With --emptytree option we assume no packages
+                        # are installed, so we do not select them.
+                        # But we allow installed packages to satisfy 
dependency requirements
+                        # if they're explicitly excluded, so we allow them to 
be selected.
+                        continue
+
                     if (
                         not pkg.installed
                         and 
self._frozen_config.excluded_pkgs.findAtomForPackage(

diff --git a/lib/portage/tests/resolver/test_depth.py 
b/lib/portage/tests/resolver/test_depth.py
index 9c5289f7d0..ab5f8e7ec3 100644
--- a/lib/portage/tests/resolver/test_depth.py
+++ b/lib/portage/tests/resolver/test_depth.py
@@ -1,4 +1,4 @@
-# Copyright 2011-2020 Gentoo Authors
+# Copyright 2011-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 from portage.tests import TestCase
@@ -318,6 +318,12 @@ class ResolverDepthTestCase(TestCase):
                     "sys-fs/udev-164",
                 ],
             ),
+            ResolverPlaygroundTestCase(
+                ["@world"],
+                options={"--emptytree": True, "--exclude": ["dev-libs/B"]},
+                success=True,
+                mergelist=["dev-libs/C-2", "dev-libs/A-2"],
+            ),
         )
 
         playground = ResolverPlayground(

diff --git 
a/lib/portage/tests/resolver/test_emptytree_reinstall_unsatisfiability.py 
b/lib/portage/tests/resolver/test_emptytree_reinstall_unsatisfiability.py
new file mode 100644
index 0000000000..fcdc01d7f1
--- /dev/null
+++ b/lib/portage/tests/resolver/test_emptytree_reinstall_unsatisfiability.py
@@ -0,0 +1,137 @@
+# Copyright 2024 Gentoo Foundation
+# 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 EmptytreeReinstallUnsatisfiabilityTestCase(TestCase):
+    def testEmptytreeReinstallUnsatisfiability(self):
+        """
+        Tests to check if emerge fails and complains when --emptytree
+        package dependency graph reinstall is unsatisfied, even if the already
+        installed packages successfully satisfy the dependency tree.
+
+        See bug #651018 where emerge silently skips package
+        reinstalls because of unsatisfied use flag requirements.
+        """
+        ebuilds = {
+            "dev-libs/A-1": {
+                "DEPEND": "dev-libs/B",
+                "RDEPEND": "dev-libs/B",
+                "EAPI": "2",
+            },
+            "dev-libs/B-1": {
+                "DEPEND": "dev-libs/C[foo]",
+                "RDEPEND": "dev-libs/C[foo]",
+                "EAPI": "2",
+            },
+            "dev-libs/C-1": {
+                "IUSE": "foo",
+                "EAPI": "2",
+            },
+            "dev-libs/X-1": {
+                "DEPEND": "dev-libs/Y[-baz]",
+                "RDEPEND": "dev-libs/Y[-baz]",
+                "EAPI": "2",
+            },
+            "dev-libs/Y-1": {
+                "IUSE": "baz",
+                "EAPI": "2",
+            },
+            "dev-libs/Z-1": {
+                "DEPEND": "dev-libs/W",
+                "RDEPEND": "dev-libs/W",
+                "EAPI": "2",
+            },
+            "dev-libs/W-1": {
+                "EAPI": "2",
+            },
+        }
+
+        installed = {
+            "dev-libs/A-1": {
+                "DEPEND": "dev-libs/B",
+                "RDEPEND": "dev-libs/B",
+                "EAPI": "2",
+            },
+            "dev-libs/B-1": {
+                "DEPEND": "dev-libs/C[foo]",
+                "RDEPEND": "dev-libs/C[foo]",
+                "EAPI": "2",
+            },
+            "dev-libs/C-1": {
+                "IUSE": "foo",
+                "USE": "foo",
+                "EAPI": "2",
+            },
+            "dev-libs/X-1": {
+                "DEPEND": "dev-libs/Y[-baz]",
+                "RDEPEND": "dev-libs/Y[-baz]",
+                "EAPI": "2",
+            },
+            "dev-libs/Y-1": {
+                "IUSE": "baz",
+                "USE": "-baz",
+                "EAPI": "2",
+            },
+            "dev-libs/Z-1": {
+                "DEPEND": "dev-libs/W",
+                "RDEPEND": "dev-libs/W",
+                "EAPI": "2",
+            },
+            "dev-libs/W-1": {
+                "EAPI": "2",
+            },
+        }
+
+        user_config = {
+            "package.use": ("dev-libs/Y baz",),
+            "package.mask": ("dev-libs/W",),
+        }
+
+        world = ["dev-libs/X"]
+
+        test_cases = (
+            ResolverPlaygroundTestCase(
+                ["dev-libs/A"],
+                options={"--emptytree": True},
+                success=False,
+                mergelist=["dev-libs/C-1", "dev-libs/B-1", "dev-libs/A-1"],
+                use_changes={"dev-libs/C-1": {"foo": True}},
+            ),
+            ResolverPlaygroundTestCase(
+                ["dev-libs/A"],
+                options={"--emptytree": True, "--exclude": ["dev-libs/C"]},
+                success=True,
+                mergelist=["dev-libs/B-1", "dev-libs/A-1"],
+            ),
+            ResolverPlaygroundTestCase(
+                ["@world"],
+                options={"--emptytree": True},
+                success=False,
+                mergelist=["dev-libs/Y-1", "dev-libs/X-1"],
+                use_changes={"dev-libs/Y-1": {"baz": False}},
+            ),
+            ResolverPlaygroundTestCase(
+                ["dev-libs/Z"],
+                options={"--emptytree": True},
+                success=False,
+            ),
+        )
+
+        playground = ResolverPlayground(
+            ebuilds=ebuilds,
+            installed=installed,
+            user_config=user_config,
+            world=world,
+        )
+        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/lib/portage/tests/resolver/test_useflags.py 
b/lib/portage/tests/resolver/test_useflags.py
index 86684f7f28..142a31c7f1 100644
--- a/lib/portage/tests/resolver/test_useflags.py
+++ b/lib/portage/tests/resolver/test_useflags.py
@@ -1,4 +1,4 @@
-# Copyright 2014 Gentoo Foundation
+# Copyright 2014-2024 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 import sys
@@ -292,8 +292,8 @@ class UseFlagsTestCase(TestCase):
                     "--usepkg": True,
                 },
                 success=False,
-                mergelist=["[binary]dev-libs/A-2", "dev-libs/B-1"],
-                slot_collision_solutions=[],
+                mergelist=None,
+                slot_collision_solutions=None,
             ),
         )
 

Reply via email to