From a12c2b558ec5a86e14d6de67afada9c656ec21e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Guzm=C3=A1n?= Date: Sat, 11 Oct 2025 01:34:12 -0600 Subject: [PATCH] ubuntu-release-upgrade: rework DistUpgradeQuirks for different Trisquel flavors. --- ...uel-sugar_to_fail_dependency_resolve.patch | 52 +++++++++------ ...ng_overcome_ecnes_new_version_scheme.patch | 24 +++++-- ...x_to_be_installed_on_desktop_systems.patch | 5 +- ...re_reinstall_desktop_meta_on_upgrade.patch | 49 ++++++++++++++ .../008-ensure_pipewire_upgrade_stack.patch | 64 +++++++++++++++++++ .../009-add_parted_transition_helper.patch | 57 +++++++++++++++++ .../010_define_independent_solver.patch | 46 +++++++++++++ helpers/make-ubuntu-release-upgrader | 14 ++-- 8 files changed, 278 insertions(+), 33 deletions(-) create mode 100644 helpers/DATA/ubuntu-release-upgrader/patch_changes/007-ensure_reinstall_desktop_meta_on_upgrade.patch create mode 100644 helpers/DATA/ubuntu-release-upgrader/patch_changes/008-ensure_pipewire_upgrade_stack.patch create mode 100644 helpers/DATA/ubuntu-release-upgrader/patch_changes/009-add_parted_transition_helper.patch create mode 100644 helpers/DATA/ubuntu-release-upgrader/patch_changes/010_define_independent_solver.patch diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/002-prevent_trisquel-sugar_to_fail_dependency_resolve.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/002-prevent_trisquel-sugar_to_fail_dependency_resolve.patch index 937587a..c16c7c4 100644 --- a/helpers/DATA/ubuntu-release-upgrader/patch_changes/002-prevent_trisquel-sugar_to_fail_dependency_resolve.patch +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/002-prevent_trisquel-sugar_to_fail_dependency_resolve.patch @@ -10,40 +10,56 @@ index f7427ac2..51f85b32 100644 self._protect_essential_gui() self._maybe_remove_gpg_wks_server() self._install_t64_replacement_packages() -@@ -205,6 +206,37 @@ class DistUpgradeQuirks(object): +@@ -205,6 +206,53 @@ class DistUpgradeQuirks(object): self._disable_cloud_init() # helpers + def _t64_transition_helper(self): + """ -+ Provides a minimal t64 transition focused on GLib and Qt5Core. -+ Marks the t64 variants for installation if the older packages are -+ installed. Idempotent and conservative for initial testing. ++ Help the resolver by preferring t64 variants for commonly-blocking libs. ++ Safe to run multiple times. + """ -+ cache = self.controller.cache -+ log = logging.getLogger("DistUpgrade") -+ -+ # Run only if at least one of the target t64 packages is in the cache. -+ if "libglib2.0-0t64" not in cache and "libqt5core5t64" not in cache: -+ log.debug("t64-helper: no target t64 packages in cache; skipping") ++ cache = getattr(self.controller, "cache", None) ++ if cache is None: + return ++ log = logging.getLogger("DistUpgrade") + + pairs = [ + ("libglib2.0-0", "libglib2.0-0t64"), + ("libqt5core5a", "libqt5core5t64"), ++ ("libelf1", "libelf1t64"), ++ ("libssl3", "libssl3t64"), ++ ("libpsl5", "libpsl5t64"), ++ ("libdb5.3", "libdb5.3t64"), ++ ("libgdbm6", "libgdbm6t64"), ++ ("libpng16-16", "libpng16-16t64"), ++ ("libtevent0", "libtevent0t64"), ++ ("libmtdev1", "libmtdev1t64"), ++ ("libts0", "libts0t64"), ++ ("libtirpc3", "libtirpc3t64"), ++ ("libqt6core6", "libqt6core6t64"), ++ ("libqt6dbus6", "libqt6dbus6t64"), ++ ("libparted2", "libparted2t64"), + ] + ++ # Run only if at least one of the target t64 packages is in the cache. ++ if not any(new in cache for _, new in pairs): ++ log.debug("t64-helper: no target t64 packages in cache; skipping") ++ return ++ + touched = [] -+ for old, new in pairs: -+ try: -+ if old in cache and cache[old].is_installed and new in cache: -+ cache[new].mark_install(True, True, False) -+ touched.append(f"{old}->{new}") -+ except Exception as e: -+ log.warning("t64-helper: error marking %s->%s: %s", old, new, e) ++ for old, newpkg in pairs: ++ if old in cache and newpkg in cache: ++ try: ++ if getattr(cache[old], "is_installed", False) or getattr(cache[old], "marked_install", False): ++ cache[newpkg].mark_install(from_user=True, auto_fix=True) ++ touched.append(newpkg) ++ except Exception as e: ++ log.debug("t64-helper: mark_install(%s) failed: %s", newpkg, e) + + if touched: -+ log.info("t64-helper: touched=%s", ", ".join(touched)) ++ log.info("t64-helper: forced %s", ", ".join(touched)) ++ + def _is_lxde_system(self): """Return True if LXDE (trisquel-mini) is detected as installed.""" diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/003-prevent_trisquel_old_versioning_overcome_ecnes_new_version_scheme.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/003-prevent_trisquel_old_versioning_overcome_ecnes_new_version_scheme.patch index 85f9166..4a6c121 100644 --- a/helpers/DATA/ubuntu-release-upgrader/patch_changes/003-prevent_trisquel_old_versioning_overcome_ecnes_new_version_scheme.patch +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/003-prevent_trisquel_old_versioning_overcome_ecnes_new_version_scheme.patch @@ -2,15 +2,25 @@ diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py index 243efc14..a63db6bb 100644 --- a/DistUpgrade/DistUpgradeQuirks.py +++ b/DistUpgrade/DistUpgradeQuirks.py -@@ -196,6 +196,8 @@ class DistUpgradeQuirks(object): - self._install_t64_replacement_packages() - self._install_pipewire_audio_on_ubuntu_studio() - self._handle_ufw_breaks() +@@ -189,6 +189,18 @@ class DistUpgradeQuirks(object): + def PreDistUpgradeCache(self): + """ run right before calculating the dist-upgrade """ + logging.debug("running Quirks.PreDistUpgradeCache") ++ + cache = self._get_cache() ++ self.__resolver = None ++ # Ensure recommends are ON for desktops, avoids resolver dead-ends ++ try: ++ self.ensure_recommends_are_installed_on_desktops() ++ except Exception as e: ++ logging.debug("ensure_recommends skipped: %s", e) ++ ++ # Prefer Ecne-style candidates before marking transitions + self._prefer_ecne_suffix(cache) - - # individual quirks handler that run *after* the dist-upgrade was - # calculated in the cache ++ + # self._install_python_is_python2() + self._t64_transition_helper() + self._protect_essential_gui() @@ -206,6 +208,88 @@ class DistUpgradeQuirks(object): self._disable_cloud_init() diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/004-add_helper_to_prevent_mta-postfix_to_be_installed_on_desktop_systems.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/004-add_helper_to_prevent_mta-postfix_to_be_installed_on_desktop_systems.patch index 1a22863..5afc47b 100644 --- a/helpers/DATA/ubuntu-release-upgrader/patch_changes/004-add_helper_to_prevent_mta-postfix_to_be_installed_on_desktop_systems.patch +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/004-add_helper_to_prevent_mta-postfix_to_be_installed_on_desktop_systems.patch @@ -41,7 +41,7 @@ index a63db6bb..c91dff31 100644 # run right before the first packages get installed def StartUpgrade(self): -@@ -186,13 +191,24 @@ class DistUpgradeQuirks(object): +@@ -193,6 +193,15 @@ class DistUpgradeQuirks(object): # individual quirks handler that run *right before* the dist-upgrade # is calculated in the cache @@ -57,6 +57,9 @@ index a63db6bb..c91dff31 100644 def PreDistUpgradeCache(self): """ run right before calculating the dist-upgrade """ logging.debug("running Quirks.PreDistUpgradeCache") +@@ -209,9 +218,11 @@ class DistUpgradeQuirks(object): + self._prefer_ecne_suffix(cache) + # self._install_python_is_python2() + self._wks_purge_from_cache() + self._mta_cancel_selection() diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/007-ensure_reinstall_desktop_meta_on_upgrade.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/007-ensure_reinstall_desktop_meta_on_upgrade.patch new file mode 100644 index 0000000..c12f842 --- /dev/null +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/007-ensure_reinstall_desktop_meta_on_upgrade.patch @@ -0,0 +1,49 @@ +diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py +index f1337e12..9250dd3a 100644 +--- a/DistUpgrade/DistUpgradeQuirks.py ++++ b/DistUpgrade/DistUpgradeQuirks.py +@@ -209,6 +209,7 @@ class DistUpgradeQuirks(object): + self._wks_purge_from_cache() + self._mta_cancel_selection() + self._t64_transition_helper() ++ self._ensure_trisquel_meta_selected() + self._protect_essential_gui() + # self._maybe_remove_gpg_wks_server() + self._install_t64_replacement_packages() +@@ -476,6 +477,36 @@ class DistUpgradeQuirks(object): + else: + logging.debug("prefer-ecne-suffix: no candidates changed") + ++ def _ensure_trisquel_meta_selected(self): ++ """ ++ Ensure base Trisquel meta-packages are explicitly selected for installation/upgrade. ++ This nudges APT's resolver to plan the transition (e.g., PipeWire replacing PulseAudio) ++ in the same run, instead of deferring meta-packages as "Not Upgrading". ++ """ ++ # Get the apt cache from the controller (attribute name varies across releases) ++ cache = getattr(self.controller, "cache", None) or getattr(self.controller, "_cache", None) ++ if cache is None: ++ # Best effort: nothing to do if cache is not yet attached here ++ return ++ ++ # Root meta-packages by flavour plus the common desktop pivot ++ roots = [ ++ "trisquel", "triskel", "trisquel-mini", "trisquel-gnome", "trisquel-sugar", ++ "trisquel-desktop-common", ++ ] ++ for name in roots: ++ try: ++ pkg = cache[name] ++ except KeyError: ++ continue # package not present in this flavour ++ if getattr(pkg, "is_installed", False): ++ try: ++ # Mark as user-requested so the resolver upgrades it with its peers ++ pkg.mark_install(from_user=True) ++ except Exception: ++ # Non-fatal: keep going ++ pass ++ + def _t64_transition_helper(self): + """ + Provides a minimal t64 transition focused on GLib and Qt5Core. diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/008-ensure_pipewire_upgrade_stack.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/008-ensure_pipewire_upgrade_stack.patch new file mode 100644 index 0000000..c605033 --- /dev/null +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/008-ensure_pipewire_upgrade_stack.patch @@ -0,0 +1,64 @@ +diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py +index 35ed55bd..b70a8899 100644 +--- a/DistUpgrade/DistUpgradeQuirks.py ++++ b/DistUpgrade/DistUpgradeQuirks.py +@@ -209,6 +209,7 @@ class DistUpgradeQuirks(object): + self._wks_purge_from_cache() + self._mta_cancel_selection() + self._t64_transition_helper() ++ self._ensure_pipewire_stack() + self._ensure_trisquel_meta_selected() + self._protect_essential_gui() + # self._maybe_remove_gpg_wks_server() +@@ -605,6 +606,51 @@ class DistUpgradeQuirks(object): + except Exception: + pass + ++ def _ensure_pipewire_stack(self): ++ """ ++ Nudge APT to pick the PipeWire stack so desktop metas can upgrade. ++ Also remove PulseAudio family to resolve Conflicts/Breaks with pipewire-audio. ++ """ ++ cache = getattr(self.controller, "cache", None) ++ if cache is None: ++ return ++ log = logging.getLogger("DistUpgrade") ++ ++ touched_i, touched_d = [], [] ++ ++ # Full PipeWire stack ++ for n in ("pipewire-audio", "pipewire-alsa", "pipewire-pulse", "gstreamer1.0-pipewire", "wireplumber"): ++ if self._apt_mark_install(n, protect=True): touched_i.append(n) ++ ++ # Remove PulseAudio family to avoid conflicts ++ for n in ("pulseaudio", "pulseaudio-module-bluetooth", "pulseaudio-module-gsettings", "pulseaudio-module-jack", "pulseaudio-utils"): ++ if self._apt_mark_delete(n): touched_d.append(n) ++ ++ if touched_i or touched_d: ++ log.info("pipewire-helper: install=%s remove=%s", ++ ", ".join(touched_i) if touched_i else "-", ++ ", ".join(touched_d) if touched_d else "-") ++ ++ ++ def mark(name): ++ if name in cache and getattr(cache[name], "candidate", None): ++ try: ++ cache[name].mark_install(from_user=True) ++ return True ++ except Exception: ++ pass ++ return False ++ ++ touched = [] ++ for n in ("pipewire-alsa", "pipewire-audio", "gstreamer1.0-pipewire"): ++ if mark(n): ++ touched.append(n) ++ ++ if touched: ++ logging.getLogger("DistUpgrade").info( ++ "pipewire-helper: preselected %s", ", ".join(touched) ++ ) ++ + def _get_pci_ids(self): + """ return a set of pci ids of the system (using lspci -n) """ + lspci = set() diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/009-add_parted_transition_helper.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/009-add_parted_transition_helper.patch new file mode 100644 index 0000000..c7299b5 --- /dev/null +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/009-add_parted_transition_helper.patch @@ -0,0 +1,57 @@ +diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py +index 484ceaae..c390003a 100644 +--- a/DistUpgrade/DistUpgradeQuirks.py ++++ b/DistUpgrade/DistUpgradeQuirks.py +@@ -209,6 +209,7 @@ class DistUpgradeQuirks(object): + self._wks_purge_from_cache() + self._mta_cancel_selection() + self._t64_transition_helper() ++ self._parted_transition_helper() + self._ensure_pipewire_stack() + self._ensure_trisquel_meta_selected() + self._protect_essential_gui() +@@ -618,6 +624,44 @@ class DistUpgradeQuirks(object): + except Exception: + pass + ++ ++ def _parted_transition_helper(self): ++ """ ++ Handle libparted/udisks2 transition seen in logs: ++ - Remove libparted-fs-resize0 (obsolete transitional) ++ - Prefer libparted2t64 when available, else libparted2 ++ - Protect libblockdev-fs2 and udisks2 from accidental removal ++ """ ++ cache = getattr(self.controller, "cache", None) ++ if cache is None: ++ return ++ log = logging.getLogger("DistUpgrade") ++ ++ touched_i, touched_d = [], [] ++ ++ if self._apt_mark_delete("libparted-fs-resize0"): ++ touched_d.append("libparted-fs-resize0") ++ ++ # Prefer t64 if present ++ if "libparted2t64" in cache: ++ if self._apt_mark_install("libparted2t64"): ++ touched_i.append("libparted2t64") ++ else: ++ if self._apt_mark_install("libparted2"): ++ touched_i.append("libparted2") ++ ++ # Keep storage stack in place ++ for keep in ("libblockdev-fs2", "udisks2"): ++ try: ++ if keep in cache and (getattr(cache[keep], "is_installed", False) or getattr(cache[keep], "marked_install", False)): ++ if self._resolver(): self._resolver().protect(cache[keep]) ++ except Exception: pass ++ ++ if touched_i or touched_d: ++ log.info("parted-helper: install=%s remove=%s", ++ ", ".join(touched_i) if touched_i else "-", ++ ", ".join(touched_d) if touched_d else "-") ++ + def _ensure_pipewire_stack(self): + """ + Nudge APT to pick the PipeWire stack so desktop metas can upgrade. diff --git a/helpers/DATA/ubuntu-release-upgrader/patch_changes/010_define_independent_solver.patch b/helpers/DATA/ubuntu-release-upgrader/patch_changes/010_define_independent_solver.patch new file mode 100644 index 0000000..7837827 --- /dev/null +++ b/helpers/DATA/ubuntu-release-upgrader/patch_changes/010_define_independent_solver.patch @@ -0,0 +1,46 @@ +diff --git a/DistUpgrade/DistUpgradeQuirks.py b/DistUpgrade/DistUpgradeQuirks.py +index 4c599207..2c3fd680 100644 +--- a/DistUpgrade/DistUpgradeQuirks.py ++++ b/DistUpgrade/DistUpgradeQuirks.py +@@ -202,6 +202,41 @@ class DistUpgradeQuirks(object): + # --- MTA hard-block config (postfix only, temporary during upgrade) --- + MTA_BLOCK_PREF = "/etc/apt/preferences.d/zz-urug-block-mta-postfix.pref" + ++ def _resolver(self): ++ # use only one ProblemResolver per stage ++ if not hasattr(self, "__resolver") or self.__resolver is None: ++ try: ++ import apt ++ self.__resolver = apt.ProblemResolver(self._get_cache()) ++ except Exception: ++ self.__resolver = None ++ return self.__resolver ++ ++ def _apt_mark_install(self, name, protect=False, auto_fix=True, from_user=True): ++ cache = self._get_cache() ++ if cache and name in cache and getattr(cache[name], "candidate", None): ++ try: ++ cache[name].mark_install(from_user=from_user, auto_fix=auto_fix) ++ if protect and self._resolver(): ++ try: self._resolver().protect(cache[name]) ++ except Exception: pass ++ return True ++ except Exception: ++ return False ++ return False ++ ++ def _apt_mark_delete(self, name, purge=False): ++ cache = self._get_cache() ++ if cache and name in cache: ++ pkg = cache[name] ++ if getattr(pkg, "is_installed", False) or getattr(pkg, "marked_install", False): ++ try: ++ pkg.mark_delete(auto_fix=False) ++ return True ++ except Exception: ++ return False ++ return False ++ + def PreDistUpgradeCache(self): + """ run right before calculating the dist-upgrade """ + logging.debug("running Quirks.PreDistUpgradeCache") diff --git a/helpers/make-ubuntu-release-upgrader b/helpers/make-ubuntu-release-upgrader index 0d5e059..d9a902b 100644 --- a/helpers/make-ubuntu-release-upgrader +++ b/helpers/make-ubuntu-release-upgrader @@ -25,7 +25,7 @@ # Also, don't forget to update the meta-release files at archive and packages.t.i # The "obsoletes" list from ubuntu has been removed -VERSION=19 +VERSION=20 . ./config # Previous upstream release name, update for each release. @@ -213,12 +213,12 @@ perl $DATA/parsewiki DistUpgrade/DevelReleaseAnnouncement > DistUpgrade/DevelRel cat < data/removal_denylist.cfg # list of packages that should never be removed -trisquel-base -trisquel-minimal -trisquel-desktop-common -trisquel-mini -triskel -trisquel-sugar +^trisquel-base$ +^trisquel-minimal$ +^trisquel-desktop-common$ +^trisquel-mini$ +^triskel$ +^trisquel-sugar$ # update-manager should not remove itself update-manager update-manager-core