Bug 54684

Summary: apt-get install не решает автоматически конфликты, если pkgCache::Version > 1
Product: Sisyphus Reporter: Denis Rastyogin <rastyoginds>
Component: aptAssignee: Ivan Zakharyaschev <imz>
Status: UNCONFIRMED --- QA Contact: qa-sisyphus
Severity: normal    
Priority: P5 CC: boyarsh, glebfm, imz, ldv, placeholder, vt
Version: unstable   
Hardware: x86_64   
OS: Linux   
Attachments:
Description Flags
патч
none
лог без таска
none
лог с таском none

Description Denis Rastyogin 2025-06-05 14:24:02 MSK
Created attachment 18751 [details]
патч

Воспроизведение:

без доп. тасков все работает
apt-get install qemu-img qemu -y &&   apt-get install pve-qemu-img -y     -o Debug::pkgProblemResolver=yes     -o Debug::pkgRemoveDepends=yes     -o Debug::pkgMarkInstall=yes &&   apt-get install -y qemu-img qemu

log_default.txt

если добавить таск (например 376145)
apt-repo add 376145
apt-get update
apt-get install qemu-img qemu -y &&   apt-get install pve-qemu-img -y     -o Debug::pkgProblemResolver=yes     -o Debug::pkgRemoveDepends=yes     -o Debug::pkgMarkInstall=yes &&   apt-get install -y qemu-img qemu

log_376145.txt

Тестировалась на 0.5.15lorg2-alt87, но на 0.5.15lorg2-alt96 проблема тоже должна быть.

Причина:

Как оказалось, проблема даже не столько в самих конфликтах, сколько в том, как apt их обрабатывает.

Конфликты есть в задании и в пакете qemu-8.2.6-alt0.p10.1, но в последнем случае apt автоматически решает их в пользу удаления. А вот в случае 376145 одновременно присутствуют две pkgCache::Version — одна относится к заданию, а другая — к P10. В результате apt проверяет обе версии.

Когда рассматривается первая версия, apt действительно планирует пометить конфликтующий пакет на удаление:

apt-pkg/algorithms.cc:
LEnd->Pkg = Pkg;
LEnd->Dep = End;
LEnd++;

Но до того, как это происходит, обрабатывается следующее условие:

// CNC:2002-07-09
if (*(V + 1) != 0) // XXX: Look for other solutions?
    continue;

То есть, если в списке версий есть другие возможные варианты, apt решает их сначала проверить и откладывает пометку на удаление.

В случае без таска других вариантов нет, и удаление в итоге происходит.

А вот в 376145, когда apt доходит до версии в репозитории, он наталкивается на такое условие:

if (Cache[Pkg].InstallVer != Ver.operator const pkgCache::Version *() &&
    (Start->Type == pkgCache::Dep::Conflicts ||
     Start->Type == pkgCache::Dep::Obsoletes))
    continue;

Это условие означает: если рассматриваемая версия не установлена, и это Conflicts или Obsoletes, то просто пропускаем её. Никаких действий не выполняется, включая удаление.

В результате — пометка на удаление, которую должны были поставить в первом случае, не ставится вообще, потому что apt решил "поискать другие варианты", а во втором случае просто вышел из проверки, не найдя установленной версии. В итоге конфликт не решается автоматически в случае с таском.

Прикладываю патч, который решает данную проблему:
0.5.15lorg2-alt87-apt-pkg-algorithms.cc-fix-kill-list-logic-to-collect.patch
Comment 1 Denis Rastyogin 2025-06-05 14:24:52 MSK
Created attachment 18752 [details]
лог без таска
Comment 2 Denis Rastyogin 2025-06-05 14:26:21 MSK
Created attachment 18753 [details]
лог с таском
Comment 3 Ivan Zakharyaschev 2025-06-10 11:51:27 MSK
Спасибо! Хорошее наблюдение и разбор кода.

Вся эта система такая запутанная... Раз Вы разобрались неплохо, может быть, Вы сможете пояснить некоторые вещи, как Вы их понимаете:

1. Почему нельзя просто сразу их все удалять?

Я думаю так: В этом цикле

	    for (pkgCache::Version * const *V = VList.get(); *V != nullptr; V++)

Start и End не меняются (на практике без булевских зависимостей совпадают?) и Start->Type будет всегда Conflicts, например. (Можно было бы не внутри цикла проверять, а снаружи, и написать два альтернативных цикла по типу зависимости вместо одного, чтобы всё не мешать в кучу. Мне сейчас кажется, так было бы понятнее.)

2. Не будет ли там нежелательного эффекта на пакеты, которые Provides "виртуальный" пакет, который подпадает под Conflicts или Obsoletes? Про Conflicts я точно не знаю, но Obsoletes, как я понимаю, на такие не должно влиять, а в VList и такие тоже лежат как результат AllTargets(), или не так?

	    const SPtrArray<pkgCache::Version * const> VList(Start.AllTargets());
Comment 4 Ivan Zakharyaschev 2025-06-10 11:53:47 MSK
В Sisyphus такой же код, там сначала исправим.
Comment 5 Denis Rastyogin 2025-06-10 18:30:41 MSK
1. Да, действительно, лучше это вынести из текущего цикла. Я побоялся того, что придется переносить некоторые условия с break, чтобы сохранить существующую логику добавления.)

2. Предполагаю, что случай с виртуальными пакетами должен исключаться уже существующим условием:

  if (Cache[Pkg].InstallVer != Ver.operator const pkgCache::Version *() &&
                      (Start->Type == pkgCache::Dep::Conflicts ||
                       Start->Type == pkgCache::Dep::Obsoletes))
                       
Мы, по сути, игнорируем здесь версии, которые не совпадают с установленными, и при Conflicts или Obsoletes не пытаемся их удалять. Значит, виртуальные пакеты в этом случае тоже не должны влиять. (так как они же не установлены?)