Skip to content

Commit

Permalink
Add a second goal for second resolving in case of unavailable packages
Browse files Browse the repository at this point in the history
An unavailable package is not a critical error, therefore, it's reported
only as a warning and it may be skipped.

One option would be to check all the specs before adding them to goal,
but that seems more error-prone.

The other option, which was chosen in this commit, is to resolve
the selection (up to) two times, the second time with skip_unavailable
set to True. It is not possible to resolve the same goal twice with
different settings, therefore, another goal is created and the specs are
added to both.

The tests are updated to match the behavior (no warnings with
sip_unavailable, warnings and not errors without skip_unavailable).
  • Loading branch information
pkratoch committed Jan 27, 2025
1 parent 123c263 commit 584a2b3
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 9 deletions.
33 changes: 30 additions & 3 deletions pyanaconda/modules/payloads/payload/dnf/dnf_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
URL_TYPE_METALINK,
URL_TYPE_MIRRORLIST,
)
from pyanaconda.core.i18n import _
from pyanaconda.core.path import join_paths
from pyanaconda.core.payload import ProxyString, ProxyStringError
from pyanaconda.core.util import get_os_release_value
Expand Down Expand Up @@ -94,6 +95,7 @@ class DNFManager:
def __init__(self):
self.__base = None
self.__goal = None
self.__goal_skip_unavailable = None

# Protect access to _base.repos to ensure that the dictionary is not
# modified while another thread is attempting to iterate over it. The
Expand Down Expand Up @@ -127,6 +129,14 @@ def _goal(self):

return self.__goal

@property
def _goal_skip_unavailable(self):
"""The DNF goal that will be used if there are unavailable packages."""
if self.__goal_skip_unavailable is None:
self.__goal_skip_unavailable = libdnf5.base.Goal(self._base)

return self.__goal_skip_unavailable

@classmethod
def _create_base(cls):
"""Create a new DNF base."""
Expand Down Expand Up @@ -189,6 +199,7 @@ def reset_base(self):
"""
self.__base = None
self.__goal = None
self.__goal_skip_unavailable = None
self._transaction = None
self._ignore_missing_packages = False
self._ignore_broken_packages = False
Expand Down Expand Up @@ -589,6 +600,7 @@ def apply_specs(self, include_list, exclude_list):
log.info("Including specs: %s", include_list)
for spec in include_list:
self._goal.add_install(spec)
self._goal_skip_unavailable.add_install(spec)

log.info("Excluding specs: %s", exclude_list)
# FIXME: Make the excludes work also for groups. Right now, only packages are excluded.
Expand All @@ -603,12 +615,27 @@ def resolve_selection(self):
log.debug("Resolving the software selection.")
self._transaction = self._goal.resolve()

if (self._transaction.get_problems() != libdnf5.base.GoalProblem_NO_PROBLEM and \
self._transaction.get_problems() == libdnf5.base.GoalProblem_NOT_FOUND):
# There is only a problem with specs that were not found, which is not critical
# -> put the logs into the warning_messages, so that user can decide to continue
# with the transaction anyway, and resolve it again with skip_unavailable.
for message in self._transaction.get_resolve_logs_as_strings():
report.warning_messages.append(message)
if not self._base.get_config().skip_unavailable:
# Temporarily set skip_unavailable to True, resolve, and set it back
self._base.get_config().skip_unavailable = True
self._transaction = self._goal_skip_unavailable.resolve()
self._base.get_config().skip_unavailable = False

if self._transaction.get_problems() != libdnf5.base.GoalProblem_NO_PROBLEM:
# There are critical errors -> put the logs into the error_messages.
report.error_messages.append(_(
"The following software marked for installation has errors.\n"
"This is likely caused by an error with your installation source.\n\n"
))
for message in self._transaction.get_resolve_logs_as_strings():
report.error_messages.append(message)
else:
for message in self._transaction.get_resolve_logs_as_strings():
report.warning_messages.append(message)

if report.is_valid():
log.info("The software selection has been resolved (%d packages selected).",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,11 @@ def test_resolve_missing_selection(self):
)

report = self.dnf_manager.resolve_selection()
assert report.error_messages == [
assert report.error_messages == []
assert report.warning_messages == [
'No match for argument: p1',
'No match for argument: g1',
]
assert report.warning_messages == []

def test_ignore_missing_packages(self):
"""Test the ignore_missing_packages attribute."""
Expand All @@ -343,10 +343,7 @@ def test_ignore_missing_packages(self):
report = self.dnf_manager.resolve_selection()

assert report.error_messages == []
assert report.warning_messages == [
'No match for argument: p1',
'No match for argument: g1',
]
assert report.warning_messages == []

@pytest.mark.skip("Not implemented")
def test_ignore_broken_packages(self):
Expand Down

0 comments on commit 584a2b3

Please sign in to comment.