[arch] improvements -> upgrading firstly the keyring packages declared in 'SyncFirst' (/etc/pacman.conf) to avoid pacman downloading issues | only removing packages after downloading the required ones

This commit is contained in:
Vinicius Moreira
2020-08-11 11:24:41 -03:00
parent b68a21894e
commit 31b67a32b7
5 changed files with 149 additions and 69 deletions

View File

@@ -11,7 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh/staging/pictures/releases/0.9.7/arch_install_reason.png">
</p>
- upgrade: only removing packages after downloading the required ones (when multi-threaded download is enabled)
- upgrade:
- upgrading firstly the keyring packages declared in **SyncFirst** (**/etc/pacman.conf**) to avoid pacman downloading issues
- only removing packages after downloading the required ones
### Fixes
- Arch

View File

@@ -58,6 +58,7 @@ class UpgradeRequirements:
self.to_remove = to_remove # when an upgrading package conflicts with a not upgrading package ( check all the non-upgrading packages deps an add here [including those selected to upgrade as well]
self.to_upgrade = to_upgrade
self.cannot_upgrade = cannot_upgrade
self.context = {} # caches relevant data to actually perform the upgrade
class TransactionResult:

View File

@@ -11,7 +11,7 @@ from datetime import datetime
from math import floor
from pathlib import Path
from threading import Thread
from typing import List, Set, Type, Tuple, Dict, Iterable
from typing import List, Set, Type, Tuple, Dict, Iterable, Optional
import requests
@@ -768,25 +768,93 @@ class ArchManager(SoftwareManager):
return files
def _upgrade_repo_pkgs(self, pkgs: List[str], handler: ProcessHandler, root_password: str, arch_config: dict, overwrite_files: bool = False,
status_handler: TransactionStatusHandler = None, skip_download: bool = False, sizes: Dict[str, int] = None, already_downloaded: int = 0) -> bool:
def list_related(self, pkgs: Iterable[str], all_pkgs: Iterable[str], data: Dict[str, dict], related: Set[str], provided_map: Dict[str, Set[str]]) -> Set[str]:
related.update(pkgs)
if not skip_download and self._should_download_packages(arch_config):
deps = set()
for pkg in pkgs:
pkg_deps = data[pkg]['d']
if pkg_deps:
deps.update({d for d in pkg_deps if d not in related})
if deps:
if not provided_map:
for p in all_pkgs:
for provided in data[p].get('p', {p}):
sources = provided_map.get(provided, set())
provided_map[provided] = sources
sources.add(p)
added_sources = set()
for dep in deps:
sources = provided_map.get(dep)
if sources:
for source in sources:
if source not in related:
related.add(source)
added_sources.add(source)
if added_sources:
self.list_related(added_sources, all_pkgs, data, related, provided_map)
return related
def _upgrade_repo_pkgs(self, to_upgrade: List[str], to_remove: Optional[Set[str]], handler: ProcessHandler, root_password: str,
multithread_download: bool, pkgs_data: Dict[str, dict], overwrite_files: bool = False,
status_handler: TransactionStatusHandler = None, sizes: Dict[str, int] = None, download: bool = True,
check_syncfirst: bool = True) -> bool:
to_sync_first = None
if check_syncfirst:
to_sync_first = [p for p in pacman.get_packages_to_sync_first() if p.endswith('-keyring') and p in to_upgrade]
if to_sync_first:
self.logger.info("Upgrading keyrings marked as 'SyncFirst'")
if not self._upgrade_repo_pkgs(to_upgrade=to_sync_first,
to_remove=None,
handler=handler,
root_password=root_password,
sizes=sizes,
download=True,
multithread_download=multithread_download,
pkgs_data=pkgs_data,
check_syncfirst=False):
return False
to_upgrade_remaining = [p for p in to_upgrade if p not in to_sync_first] if to_sync_first else to_upgrade
# pre-downloading all packages before removing any
if download:
try:
downloaded = self._download_packages(pkgs, handler, root_password, sizes)
downloaded = self._download_packages(pkgnames=to_upgrade_remaining,
handler=handler,
root_password=root_password,
sizes=sizes,
multithreaded=multithread_download)
if downloaded < len(to_upgrade_remaining):
self._show_upgrade_download_failed(handler.watcher)
return False
except ArchDownloadException:
self._show_upgrade_download_failed(handler.watcher)
return False
else:
downloaded = already_downloaded
if to_remove and not self._remove_transaction_packages(to_remove, handler, root_password):
return False
try:
if status_handler:
output_handler = status_handler
else:
output_handler = TransactionStatusHandler(handler.watcher, self.i18n, len(pkgs), self.logger, downloading=downloaded)
output_handler = TransactionStatusHandler(handler.watcher, self.i18n, len(to_upgrade), self.logger, downloading=len(to_upgrade_remaining))
output_handler.start()
success, upgrade_output = handler.handle_simple(pacman.upgrade_several(pkgnames=pkgs,
self.logger.info("Upgrading {} repository packages: {}".format(len(to_upgrade), ', '.join(to_upgrade)))
success, upgrade_output = handler.handle_simple(pacman.upgrade_several(pkgnames=to_upgrade_remaining,
root_password=root_password,
overwrite_conflicting_files=overwrite_files),
output_handler=output_handler.handle,)
@@ -797,10 +865,10 @@ class ArchManager(SoftwareManager):
output_handler.join()
handler.watcher.print("Repository packages successfully upgraded")
handler.watcher.change_substatus(self.i18n['arch.upgrade.caching_pkgs_data'])
repo_map = pacman.map_repositories(pkgs)
repo_map = pacman.map_repositories(to_upgrade_remaining)
pkg_map = {}
for name in pkgs:
for name in to_upgrade_remaining:
repo = repo_map.get(name)
pkg_map[name] = ArchPackage(name=name,
repository=repo,
@@ -817,13 +885,16 @@ class ArchManager(SoftwareManager):
confirmation_label=self.i18n['arch.upgrade.conflicting_files.stop'],
components=files):
return self._upgrade_repo_pkgs(pkgs=pkgs,
return self._upgrade_repo_pkgs(to_upgrade=to_upgrade_remaining,
handler=handler,
root_password=root_password,
overwrite_files=True,
status_handler=output_handler,
skip_download=True,
arch_config=arch_config,
multithread_download=multithread_download,
download=False,
check_syncfirst=False,
pkgs_data=pkgs_data,
to_remove=None,
sizes=sizes)
else:
output_handler.stop_working()
@@ -842,27 +913,26 @@ class ArchManager(SoftwareManager):
traceback.print_exc()
return False
def _remove_transaction_packages(self, requirements: UpgradeRequirements, handler: ProcessHandler, root_password: str) -> bool:
to_remove_names = {r.pkg.name for r in requirements.to_remove}
def _remove_transaction_packages(self, to_remove: Set[str], handler: ProcessHandler, root_password: str) -> bool:
output_handler = TransactionStatusHandler(watcher=handler.watcher,
i18n=self.i18n,
pkgs_to_sync=0,
logger=self.logger,
pkgs_to_remove=len(to_remove_names))
pkgs_to_remove=len(to_remove))
output_handler.start()
try:
success = handler.handle(pacman.remove_several(pkgnames=to_remove_names, root_password=root_password, skip_checks=True),
success = handler.handle(pacman.remove_several(pkgnames=to_remove, root_password=root_password, skip_checks=True),
output_handler=output_handler.handle)
if not success:
self.logger.error("Could not remove packages: {}".format(', '.join(to_remove_names)))
self.logger.error("Could not remove packages: {}".format(', '.join(to_remove)))
output_handler.stop_working()
output_handler.join()
return False
return True
except:
self.logger.error("An error occured while removing packages: {}".format(', '.join(to_remove_names)))
self.logger.error("An error occured while removing packages: {}".format(', '.join(to_remove)))
traceback.print_exc()
output_handler.stop_working()
output_handler.join()
@@ -899,45 +969,19 @@ class ArchManager(SoftwareManager):
arch_config = read_config()
self._sync_databases(arch_config=arch_config, root_password=root_password, handler=handler)
# for now if the multithreaded-download is disabled packages will be removed before the download:
multithreaded_download = self._should_download_packages(arch_config)
if requirements.to_remove and (not repo_pkgs or not multithreaded_download):
if not self._remove_transaction_packages(requirements, handler, root_password):
return False
if repo_pkgs:
repo_pkgs_names = [p.name for p in repo_pkgs]
downloaded = -1
if requirements.to_remove and multithreaded_download: # pre-downloading all packages before removing any
try:
downloaded = self._download_packages(repo_pkgs_names, handler, root_password, pkg_sizes)
if downloaded < len(repo_pkgs_names):
self._show_upgrade_download_failed(handler.watcher)
return False
except ArchDownloadException:
self._show_upgrade_download_failed(handler.watcher)
return False
if not self._remove_transaction_packages(requirements, handler, root_password):
return False
watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_repo_pkgs']))
self.logger.info("Upgrading {} repository packages: {}".format(len(repo_pkgs_names),
', '.join(repo_pkgs_names)))
if not self._upgrade_repo_pkgs(pkgs=repo_pkgs_names,
if not self._upgrade_repo_pkgs(to_upgrade=[p.name for p in repo_pkgs],
to_remove={r.pkg.name for r in requirements.to_remove} if requirements.to_remove else None,
handler=handler,
root_password=root_password,
arch_config=arch_config,
skip_download=downloaded >= 0,
already_downloaded=0 if downloaded < 0 else downloaded,
multithread_download=self._multithreaded_download_enabled(arch_config),
pkgs_data=requirements.context['data'],
sizes=pkg_sizes):
return False
elif requirements.to_remove and not self._remove_transaction_packages({r.pkg.name for r in requirements.to_remove}, handler, root_password):
return False
if aur_pkgs:
watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs']))
for pkg in aur_pkgs:
@@ -1450,10 +1494,10 @@ class ArchManager(SoftwareManager):
return {dep}
downloaded = 0
if self._should_download_packages(context.config):
if self._multithreaded_download_enabled(context.config):
try:
pkg_sizes = pacman.map_download_sizes(repo_dep_names)
downloaded = self._download_packages(repo_dep_names, context.handler, context.root_password, pkg_sizes)
downloaded = self._download_packages(repo_dep_names, context.handler, context.root_password, pkg_sizes, multithreaded=True)
except ArchDownloadException:
return False
@@ -1756,21 +1800,36 @@ class ArchManager(SoftwareManager):
return True
def _should_download_packages(self, arch_config: dict) -> bool:
def _multithreaded_download_enabled(self, arch_config: dict) -> bool:
return bool(arch_config['repositories_mthread_download']) \
and self.context.file_downloader.is_multithreaded() \
and pacman.is_mirrors_available()
def _download_packages(self, pkgnames: List[str], handler: ProcessHandler, root_password: str, sizes: Dict[str, int] = None) -> int:
download_service = MultithreadedDownloadService(file_downloader=self.context.file_downloader,
http_client=self.http_client,
logger=self.context.logger,
i18n=self.i18n)
self.logger.info("Initializing multi-threaded download for {} package(s)".format(len(pkgnames)))
return download_service.download_packages(pkgs=pkgnames,
handler=handler,
sizes=sizes,
root_password=root_password)
def _download_packages(self, pkgnames: List[str], handler: ProcessHandler, root_password: str, sizes: Dict[str, int] = None, multithreaded: bool = True) -> int:
if multithreaded:
download_service = MultithreadedDownloadService(file_downloader=self.context.file_downloader,
http_client=self.http_client,
logger=self.context.logger,
i18n=self.i18n)
self.logger.info("Initializing multi-threaded download for {} repository package(s)".format(len(pkgnames)))
return download_service.download_packages(pkgs=pkgnames,
handler=handler,
sizes=sizes,
root_password=root_password)
else:
self.logger.info("Downloading {} repository package(s)".format(len(pkgnames)))
output_handler = TransactionStatusHandler(handler.watcher, self.i18n, len(pkgnames), self.logger)
output_handler.start()
try:
success, _ = handler.handle_simple(pacman.download(root_password, *pkgnames), output_handler=output_handler.handle)
if success:
return len(pkgnames)
else:
raise ArchDownloadException()
except:
traceback.print_exc()
raise ArchDownloadException()
def _install(self, context: TransactionContext) -> bool:
check_install_output = []
@@ -1830,13 +1889,13 @@ class ArchManager(SoftwareManager):
to_install.append(pkgpath)
downloaded = 0
if self._should_download_packages(context.config):
if self._multithreaded_download_enabled(context.config):
to_download = [p for p in to_install if not p.startswith('/')]
if to_download:
try:
pkg_sizes = pacman.map_download_sizes(to_download)
downloaded = self._download_packages(to_download, context.handler, context.root_password, pkg_sizes)
downloaded = self._download_packages(to_download, context.handler, context.root_password, pkg_sizes, multithreaded=True)
except ArchDownloadException:
return False

View File

@@ -18,6 +18,7 @@ RE_DOWNLOAD_SIZE = re.compile(r'Download Size\s*:\s*([0-9,\.]+)\s(\w+)\n?', re.I
RE_UPDATE_REQUIRED_FIELDS = re.compile(r'(\bProvides\b|\bInstalled Size\b|\bConflicts With\b)\s*:\s(.+)\n')
RE_REMOVE_TRANSITIVE_DEPS = re.compile(r'removing\s([\w\-_]+)\s.+required\sby\s([\w\-_]+)\n?')
RE_AVAILABLE_MIRRORS = re.compile(r'.+\s+OK\s+.+\s+(\d+:\d+)\s+.+(http.+)')
RE_PACMAN_SYNC_FIRST = re.compile(r'SyncFirst\s*=\s*(.+)')
def is_available() -> bool:
@@ -733,6 +734,12 @@ def upgrade_several(pkgnames: Iterable[str], root_password: str, overwrite_confl
error_phrases={'error: failed to prepare transaction', 'error: failed to commit transaction', 'error: target not found'})
def download(root_password: str, *pkgnames: str) -> SimpleProcess:
return SimpleProcess(cmd=['pacman', '-Swdd', *pkgnames, '--noconfirm'],
root_password=root_password,
error_phrases={'error: failed to prepare transaction', 'error: failed to commit transaction', 'error: target not found'})
def remove_several(pkgnames: Iterable[str], root_password: str, skip_checks: bool = False) -> SystemProcess:
cmd = ['pacman', '-R', *pkgnames, '--noconfirm']
@@ -1119,3 +1126,13 @@ def get_mirrors_branch() -> str:
_, output = system.run(['pacman-mirrors', '-G'])
return output.strip()
def get_packages_to_sync_first() -> Set[str]:
if os.path.exists('/etc/pacman.conf'):
with open('/etc/pacman.conf') as f:
to_sync_first = RE_PACMAN_SYNC_FIRST.findall(f.read())
if to_sync_first:
return {s.strip() for s in to_sync_first[0].split(' ') if s and s.strip()}
return set()

View File

@@ -424,6 +424,7 @@ class UpdatesSummarizer:
to_sync.update(context.to_install.keys())
res.to_install = [self._map_requirement(p, context, to_install=True, to_sync=to_sync) for p in context.to_install.values()]
res.context['data'] = context.pkgs_data
return res
def __update_context_based_on_to_remove(self, context: UpdateRequirementsContext):