public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Zac Medico" <zmedico@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/portage:master commit in: lib/portage/tests/resolver/, lib/_emerge/
Date: Mon,  8 Jan 2024 08:58:34 +0000 (UTC)	[thread overview]
Message-ID: <1704701331.4b885b9ca063c990b1e218c73a786e9d434717e8.zmedico@gentoo> (raw)

commit:     4b885b9ca063c990b1e218c73a786e9d434717e8
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Mon Jan  8 06:04:37 2024 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Mon Jan  8 08:08:51 2024 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=4b885b9c

_calc_depclean: add dep_check action

Add a dep_check action which can be used to check the
dependencies of all installed packages. The plan is for depgraph
to use this action to check for broken dependencies prior to the
merge order calculation. The new frozen_config parameter will
allow depgraph to pass a shared frozen config to _calc_depclean.

The result of the dep_check action becomes stale as soon as there
is any change to the installed packages. So, in order to account
for dependencies that may become broken or satisfied during the
process of updating installed packages, the merge order
calculation will need to refresh the dep_check calculation for
every merge order choice that it makes. This refresh will need
to be optimized to identify the portion of the graph that would
become stale due to a given change, so that it can avoid
unnecessary repetition of work.

Bug: https://bugs.gentoo.org/921333
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/actions.py                           | 21 ++++++-
 lib/_emerge/depgraph.py                          |  7 ++-
 lib/portage/tests/resolver/ResolverPlayground.py | 43 ++++++++++++--
 lib/portage/tests/resolver/meson.build           |  1 +
 lib/portage/tests/resolver/test_broken_deps.py   | 76 ++++++++++++++++++++++++
 5 files changed, 138 insertions(+), 10 deletions(-)

diff --git a/lib/_emerge/actions.py b/lib/_emerge/actions.py
index 20f3978f77..2710c4856c 100644
--- a/lib/_emerge/actions.py
+++ b/lib/_emerge/actions.py
@@ -909,7 +909,16 @@ _depclean_result = collections.namedtuple(
 )
 
 
-def _calc_depclean(settings, trees, ldpath_mtimes, myopts, action, args_set, spinner):
+def _calc_depclean(
+    settings,
+    trees,
+    ldpath_mtimes,
+    myopts,
+    action,
+    args_set,
+    spinner,
+    frozen_config=None,
+):
     allow_missing_deps = bool(args_set)
 
     debug = "--debug" in myopts
@@ -988,12 +997,14 @@ def _calc_depclean(settings, trees, ldpath_mtimes, myopts, action, args_set, spi
 
     writemsg_level("\nCalculating dependencies  ")
     resolver_params = create_depgraph_params(myopts, "remove")
-    resolver = depgraph(settings, trees, myopts, resolver_params, spinner)
+    resolver = depgraph(
+        settings, trees, myopts, resolver_params, spinner, frozen_config=frozen_config
+    )
     resolver._load_vdb()
     vardb = resolver._frozen_config.trees[eroot]["vartree"].dbapi
     real_vardb = trees[eroot]["vartree"].dbapi
 
-    if action == "depclean":
+    if action in ("dep_check", "depclean"):
         if args_set:
             if deselect:
                 # Start with an empty set.
@@ -1002,6 +1013,7 @@ def _calc_depclean(settings, trees, ldpath_mtimes, myopts, action, args_set, spi
                 # Pull in any sets nested within the selected set.
                 selected_set.update(psets["selected"].getNonAtoms())
 
+        if args_set or action == "dep_check":
             # Pull in everything that's installed but not matched
             # by an argument atom since we don't want to clean any
             # package if something depends on it.
@@ -1098,6 +1110,9 @@ def _calc_depclean(settings, trees, ldpath_mtimes, myopts, action, args_set, spi
     if not success:
         return _depclean_result(1, [], False, 0, resolver)
 
+    if action == "dep_check":
+        return _depclean_result(0, [], False, 0, resolver)
+
     def unresolved_deps():
         soname_deps = set()
         unresolvable = set()

diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index a2865cad23..b859e68224 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -11723,6 +11723,7 @@ def backtrack_depgraph(
     myaction: Optional[str],
     myfiles: list[str],
     spinner: "_emerge.stdout_spinner.stdout_spinner",
+    frozen_config: Optional[_frozen_depgraph_config] = None,
 ) -> tuple[Any, depgraph, list[str]]:
     """
 
@@ -11747,6 +11748,7 @@ def _backtrack_depgraph(
     myaction: Optional[str],
     myfiles: list[str],
     spinner: "_emerge.stdout_spinner.stdout_spinner",
+    frozen_config: Optional[_frozen_depgraph_config] = None,
 ) -> tuple[Any, depgraph, list[str], int, int]:
     debug = "--debug" in myopts
     mydepgraph = None
@@ -11756,7 +11758,10 @@ def _backtrack_depgraph(
     backtracker = Backtracker(max_depth)
     backtracked = 0
 
-    frozen_config = _frozen_depgraph_config(settings, trees, myopts, myparams, spinner)
+    if frozen_config is None:
+        frozen_config = _frozen_depgraph_config(
+            settings, trees, myopts, myparams, spinner
+        )
 
     while backtracker:
         if debug and mydepgraph is not None:

diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py
index 592f5cc5dc..2d26012873 100644
--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2023 Gentoo Authors
+# Copyright 2010-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import bz2
@@ -33,7 +33,11 @@ from _emerge.actions import _calc_depclean
 from _emerge.Blocker import Blocker
 from _emerge.create_depgraph_params import create_depgraph_params
 from _emerge.DependencyArg import DependencyArg
-from _emerge.depgraph import backtrack_depgraph
+from _emerge.depgraph import (
+    _frozen_depgraph_config,
+    backtrack_depgraph,
+)
+from _emerge.Package import Package
 from _emerge.RootConfig import RootConfig
 
 
@@ -732,7 +736,16 @@ class ResolverPlayground:
                 portage.util.noiselimit = -2
             _emerge.emergelog._disable = True
 
-            if action in ("depclean", "prune"):
+            # NOTE: frozen_config could be cached and reused if options and params were constant.
+            params_action = (
+                "remove" if action in ("dep_check", "depclean", "prune") else action
+            )
+            params = create_depgraph_params(options, params_action)
+            frozen_config = _frozen_depgraph_config(
+                self.settings, self.trees, options, params, None
+            )
+
+            if params_action == "remove":
                 depclean_result = _calc_depclean(
                     self.settings,
                     self.trees,
@@ -741,6 +754,7 @@ class ResolverPlayground:
                     action,
                     InternalPackageSet(initial_atoms=atoms, allow_wildcard=True),
                     None,
+                    frozen_config=frozen_config,
                 )
                 result = ResolverPlaygroundDepcleanResult(
                     atoms,
@@ -751,9 +765,15 @@ class ResolverPlayground:
                     depclean_result.depgraph,
                 )
             else:
-                params = create_depgraph_params(options, action)
                 success, depgraph, favorites = backtrack_depgraph(
-                    self.settings, self.trees, options, params, action, atoms, None
+                    self.settings,
+                    self.trees,
+                    options,
+                    params,
+                    action,
+                    atoms,
+                    None,
+                    frozen_config=frozen_config,
                 )
                 depgraph._show_merge_list()
                 depgraph.display_problems()
@@ -939,7 +959,8 @@ class ResolverPlaygroundTestCase:
                 )
                 and expected is not None
             ):
-                expected = set(expected)
+                # unsatisfied_deps can be a dict for depclean-like actions
+                expected = expected if isinstance(expected, dict) else set(expected)
 
             elif key == "forced_rebuilds" and expected is not None:
                 expected = {k: set(v) for k, v in expected.items()}
@@ -1109,11 +1130,14 @@ class ResolverPlaygroundDepcleanResult:
         "ordered",
         "req_pkg_count",
         "graph_order",
+        "unsatisfied_deps",
     )
     optional_checks = (
+        "cleanlist",
         "ordered",
         "req_pkg_count",
         "graph_order",
+        "unsatisfied_deps",
     )
 
     def __init__(self, atoms, rval, cleanlist, ordered, req_pkg_count, depgraph):
@@ -1125,3 +1149,10 @@ class ResolverPlaygroundDepcleanResult:
         self.graph_order = [
             _mergelist_str(node, depgraph) for node in depgraph._dynamic_config.digraph
         ]
+        self.unsatisfied_deps = {}
+        for dep in depgraph._dynamic_config._initially_unsatisfied_deps:
+            if isinstance(dep.parent, Package):
+                parent_repr = dep.parent.cpv
+            else:
+                parent_repr = dep.parent.arg
+            self.unsatisfied_deps.setdefault(parent_repr, set()).add(dep.atom)

diff --git a/lib/portage/tests/resolver/meson.build b/lib/portage/tests/resolver/meson.build
index 77c65a511e..8892c78131 100644
--- a/lib/portage/tests/resolver/meson.build
+++ b/lib/portage/tests/resolver/meson.build
@@ -15,6 +15,7 @@ py.install_sources(
         'test_bdeps.py',
         'test_binary_pkg_ebuild_visibility.py',
         'test_blocker.py',
+        'test_broken_deps.py',
         'test_changed_deps.py',
         'test_circular_choices.py',
         'test_circular_choices_rust.py',

diff --git a/lib/portage/tests/resolver/test_broken_deps.py b/lib/portage/tests/resolver/test_broken_deps.py
new file mode 100644
index 0000000000..8ca7809d34
--- /dev/null
+++ b/lib/portage/tests/resolver/test_broken_deps.py
@@ -0,0 +1,76 @@
+# Copyright 2024 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 BrokenDepsTestCase(TestCase):
+    def testBrokenDeps(self):
+        """
+        Test the _calc_depclean "dep_check" action which will eventually
+        be used to check for unsatisfied deps of installed packages
+        for bug 921333.
+        """
+        ebuilds = {
+            "dev-qt/qtcore-5.15.12": {
+                "EAPI": "8",
+            },
+            "dev-qt/qtcore-5.15.11-r1": {
+                "EAPI": "8",
+            },
+            "dev-qt/qtxmlpatterns-5.15.12": {
+                "EAPI": "8",
+                "DEPEND": "=dev-qt/qtcore-5.15.12*",
+                "RDEPEND": "=dev-qt/qtcore-5.15.12*",
+            },
+            "dev-qt/qtxmlpatterns-5.15.11": {
+                "EAPI": "8",
+                "DEPEND": "=dev-qt/qtcore-5.15.11*",
+                "RDEPEND": "=dev-qt/qtcore-5.15.11*",
+            },
+            "kde-frameworks/syntax-highlighting-5.113.0": {
+                "EAPI": "8",
+                "DEPEND": ">=dev-qt/qtxmlpatterns-5.15.9:5",
+            },
+        }
+        installed = {
+            "dev-qt/qtcore-5.15.12": {
+                "EAPI": "8",
+            },
+            "dev-qt/qtxmlpatterns-5.15.11": {
+                "EAPI": "8",
+                "DEPEND": "=dev-qt/qtcore-5.15.11*",
+                "RDEPEND": "=dev-qt/qtcore-5.15.11*",
+            },
+            "kde-frameworks/syntax-highlighting-5.113.0": {
+                "EAPI": "8",
+                "DEPEND": ">=dev-qt/qtxmlpatterns-5.15.9:5",
+            },
+        }
+
+        world = ("kde-frameworks/syntax-highlighting",)
+
+        test_cases = (
+            ResolverPlaygroundTestCase(
+                [],
+                action="dep_check",
+                success=True,
+                unsatisfied_deps={
+                    "dev-qt/qtxmlpatterns-5.15.11": {"=dev-qt/qtcore-5.15.11*"}
+                },
+            ),
+        )
+
+        playground = ResolverPlayground(
+            ebuilds=ebuilds, installed=installed, 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()


             reply	other threads:[~2024-01-08  8:58 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-08  8:58 Zac Medico [this message]
  -- strict thread matches above, loose matches on Subject: below --
2023-12-26 21:05 [gentoo-commits] proj/portage:master commit in: lib/portage/tests/resolver/, lib/_emerge/ Zac Medico
2023-11-29 19:55 Zac Medico
2023-11-25  6:30 Zac Medico
2021-01-11  7:27 Zac Medico
2020-12-02  8:32 Zac Medico
2020-08-31  6:22 Zac Medico
2020-04-12  1:52 Zac Medico
2020-03-14 20:57 Zac Medico
2020-02-15  0:58 Zac Medico
2019-12-26 23:00 Zac Medico
2019-12-06  4:06 Zac Medico
2019-11-26 20:35 Zac Medico

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1704701331.4b885b9ca063c990b1e218c73a786e9d434717e8.zmedico@gentoo \
    --to=zmedico@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox