[arch] improvement -> AUR: new settings property 'aur_build_only_chosen'

This commit is contained in:
Vinicius Moreira
2020-08-31 15:01:20 -03:00
parent fede4fb210
commit 1418383730
16 changed files with 271 additions and 61 deletions

View File

@@ -43,6 +43,7 @@ from bauh.gems.arch.exceptions import PackageNotFoundException
from bauh.gems.arch.mapper import ArchDataMapper
from bauh.gems.arch.model import ArchPackage
from bauh.gems.arch.output import TransactionStatusHandler
from bauh.gems.arch.pacman import RE_DEP_OPERATORS
from bauh.gems.arch.updates import UpdatesSummarizer
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, SyncDatabases, \
RefreshMirrors
@@ -56,6 +57,8 @@ RE_SPLIT_VERSION = re.compile(r'([=><]+)')
SOURCE_FIELDS = ('source', 'source_x86_64')
RE_PRE_DOWNLOAD_WL_PROTOCOLS = re.compile(r'^(.+::)?(https?|ftp)://.+')
RE_PRE_DOWNLOAD_BL_EXT = re.compile(r'.+\.(git|gpg)$')
RE_PKGBUILD_PKGNAME = re.compile(r'pkgname\s*=.+')
RE_CONFLICT_DETECTED = re.compile(r'\n::\s*(.+)\s+are in conflict\s*.')
class TransactionContext:
@@ -63,12 +66,13 @@ class TransactionContext:
def __init__(self, name: str = None, base: str = None, maintainer: str = None, watcher: ProcessWatcher = None,
handler: ProcessHandler = None, dependency: bool = None, skip_opt_deps: bool = False, root_password: str = None,
build_dir: str = None, project_dir: str = None, change_progress: bool = False, arch_config: dict = None,
install_file: str = None, repository: str = None, pkg: ArchPackage = None,
install_files: Set[str] = None, repository: str = None, pkg: ArchPackage = None,
remote_repo_map: Dict[str, str] = None, provided_map: Dict[str, Set[str]] = None,
remote_provided_map: Dict[str, Set[str]] = None, aur_idx: Set[str] = None,
missing_deps: List[Tuple[str, str]] = None, installed: Set[str] = None, removed: Dict[str, SoftwarePackage] = None,
disk_loader: DiskCacheLoader = None, disk_cache_updater: Thread = None,
new_pkg: bool = False):
new_pkg: bool = False, custom_pkgbuild_path: str = None,
pkgs_to_build: Set[str] = None):
self.name = name
self.base = base
self.maintainer = maintainer
@@ -82,7 +86,7 @@ class TransactionContext:
self.change_progress = change_progress
self.repository = repository
self.config = arch_config
self.install_file = install_file
self.install_files = install_files
self.pkg = pkg
self.provided_map = provided_map
self.remote_repo_map = remote_repo_map
@@ -95,6 +99,8 @@ class TransactionContext:
self.disk_cache_updater = disk_cache_updater
self.pkgbuild_edited = False
self.new_pkg = new_pkg
self.custom_pkgbuild_path = custom_pkgbuild_path
self.pkgs_to_build = pkgs_to_build
@classmethod
def gen_context_from(cls, pkg: ArchPackage, arch_config: dict, root_password: str, handler: ProcessHandler) -> "TransactionContext":
@@ -123,11 +129,14 @@ class TransactionContext:
dep_context.removed = {}
return dep_context
def has_install_file(self) -> bool:
return self.install_file is not None
def has_install_files(self) -> bool:
return bool(self.install_files)
def get_package_path(self) -> str:
return self.install_file if self.install_file else self.name
def get_packages_paths(self) -> Set[str]:
return self.install_files if self.install_files else {self.name}
def get_package_names(self) -> Set[str]:
return self.pkgs_to_build if (self.pkgs_to_build and self.install_files) else {self.name}
def get_version(self) -> str:
return self.pkg.version if self.pkg else None
@@ -693,7 +702,7 @@ class ArchManager(SoftwareManager):
context.watcher.change_progress(50)
context.install_file = version_files[versions[0]]
context.install_files = version_files[versions[0]] # TODO verify
if not self._handle_missing_deps(context=context):
return False
@@ -1654,11 +1663,26 @@ class ArchManager(SoftwareManager):
'description': 'pkgdesc'}.items():
setattr(context.pkg, pkgattr, srcinfo.get(srcattr, getattr(context.pkg, pkgattr)))
def _read_srcinfo(self, context: TransactionContext) -> str:
src_path = '{}/.SRCINFO'.format(context.project_dir)
if not os.path.exists(src_path):
srcinfo = makepkg.gen_srcinfo(context.project_dir, context.custom_pkgbuild_path)
with open(src_path, 'w+') as f:
f.write(srcinfo)
else:
with open(src_path) as f:
srcinfo = f.read()
return srcinfo
def _build(self, context: TransactionContext) -> bool:
self._edit_pkgbuild_and_update_context(context)
self._pre_download_source(context.name, context.project_dir, context.watcher)
self._update_progress(context, 50)
context.custom_pkgbuild_path = self._gen_custom_pkgbuild_if_required(context)
if not self._handle_aur_package_deps_and_keys(context):
return False
@@ -1673,7 +1697,10 @@ class ArchManager(SoftwareManager):
cpu_optimized = True
try:
pkgbuilt, output = makepkg.make(context.project_dir, optimize=optimize, handler=context.handler)
pkgbuilt, output = makepkg.make(pkgdir=context.project_dir,
optimize=optimize,
handler=context.handler,
custom_pkgbuild=context.custom_pkgbuild_path)
finally:
if cpu_optimized:
self.logger.info("Setting cpus to powersave mode")
@@ -1682,7 +1709,32 @@ class ArchManager(SoftwareManager):
self._update_progress(context, 65)
if pkgbuilt:
gen_file = [fname for root, dirs, files in os.walk(context.build_dir) for fname in files if re.match(r'^{}-.+\.tar\.(xz|zst)'.format(context.name), fname)]
self.__fill_aur_output_files(context)
if self._install(context=context):
self._save_pkgbuild(context)
if context.dependency or context.skip_opt_deps:
return True
context.watcher.change_substatus(self.i18n['arch.optdeps.checking'].format(bold(context.name)))
self._update_progress(context, 100)
if self._install_optdeps(context):
return True
return False
def __fill_aur_output_files(self, context: TransactionContext):
self.logger.info("Determining output files of '{}'".format(context.name))
context.watcher.change_substatus(self.i18n['arch.aur.build.list_output'])
output_files = {f for f in makepkg.list_output_files(context.project_dir, context.custom_pkgbuild_path) if os.path.isfile(f)}
if output_files:
context.install_files = output_files
else:
gen_file = [fname for root, dirs, files in os.walk(context.build_dir) for fname in files if
re.match(r'^{}-.+\.tar\.(xz|zst)'.format(context.name), fname)]
if not gen_file:
context.watcher.print('Could not find the built package. Aborting...')
@@ -1702,21 +1754,9 @@ class ArchManager(SoftwareManager):
if perfect_match:
file_to_install = perfect_match[0]
context.install_file = '{}/{}'.format(context.project_dir, file_to_install)
context.install_files = {'{}/{}'.format(context.project_dir, file_to_install)}
if self._install(context=context):
self._save_pkgbuild(context)
if context.dependency or context.skip_opt_deps:
return True
context.watcher.change_substatus(self.i18n['arch.optdeps.checking'].format(bold(context.name)))
self._update_progress(context, 100)
if self._install_optdeps(context):
return True
return False
context.watcher.change_substatus('')
def _save_pkgbuild(self, context: TransactionContext):
cache_path = ArchPackage.disk_cache_path(context.name)
@@ -1761,13 +1801,27 @@ class ArchManager(SoftwareManager):
ti = time.time()
if context.repository == 'aur':
with open('{}/.SRCINFO'.format(context.project_dir)) as f:
srcinfo = aur.map_srcinfo(string=f.read(), pkgname=context.name)
srcinfo = aur.map_srcinfo(string=self._read_srcinfo(context),
pkgname=context.name if (not context.pkgs_to_build or len(context.pkgs_to_build) == 1) else None)
if context.pkgs_to_build and len(context.pkgs_to_build) > 1: # removing self dependencies from srcinfo
for attr in ('depends', 'makedepends', 'optdepends'):
dep_list = srcinfo.get(attr)
if dep_list and isinstance(dep_list, list):
to_remove = set()
for dep in dep_list:
dep_name = RE_DEP_OPERATORS.split(dep.split(':')[0])[0].strip()
if dep_name and dep_name in context.pkgs_to_build:
to_remove.add(dep)
for dep in to_remove:
dep_list.remove(dep)
pkgs_data = {context.name: self.aur_client.map_update_data(context.name, context.get_version(), srcinfo)}
else:
file = bool(context.install_file)
pkgs_data = pacman.map_updates_data({context.install_file if file else context.name}, files=file)
pkgs_data = pacman.map_updates_data(context.get_packages_paths(), files=bool(context.install_files))
deps_data, alread_checked_deps = {}, set()
@@ -1807,7 +1861,8 @@ class ArchManager(SoftwareManager):
check_res = makepkg.check(context.project_dir,
optimize=bool(context.config['optimize']),
missing_deps=False,
handler=context.handler)
handler=context.handler,
custom_pkgbuild=context.custom_pkgbuild_path)
if check_res:
if check_res.get('gpg_key'):
@@ -1952,22 +2007,28 @@ class ArchManager(SoftwareManager):
raise ArchDownloadException()
def _install(self, context: TransactionContext) -> bool:
check_install_output = []
pkgpath = context.get_package_path()
pkgpaths = context.get_packages_paths()
context.watcher.change_substatus(self.i18n['arch.checking.conflicts'].format(bold(context.name)))
self.logger.info("Checking for possible conflicts with '{}'".format(context.name))
for check_out in SimpleProcess(cmd=['pacman', '-U' if context.install_file else '-S', pkgpath],
root_password=context.root_password,
cwd=context.project_dir or '.').instance.stdout:
check_install_output.append(check_out.decode())
_, output = context.handler.handle_simple(pacman.install_as_process(pkgpaths=pkgpaths,
root_password=context.root_password,
pkgdir=context.project_dir or '.',
file=bool(context.install_files),
simulate=True),
notify_watcher=False)
# for check_out in SimpleProcess(cmd=['pacman', '-U' if context.install_files else '-S', pkgpath],
# root_password=context.root_password,
# cwd=context.project_dir or '.').instance.stdout:
# check_install_output.append(check_out.decode())
self._update_progress(context, 70)
if check_install_output and 'conflict' in check_install_output[-1]:
if 'unresolvable package conflicts detected' in output:
self.logger.info("Conflicts detected for '{}'".format(context.name))
conflicting_apps = [w[0] for w in re.findall(r'((\w|\-|\.)+)\s(and|are)', check_install_output[-1])]
conflict_msgs = RE_CONFLICT_DETECTED.findall(output)
conflicting_apps = {n.strip() for m in conflict_msgs for n in m.split(' and ')}
conflict_msg = ' {} '.format(self.i18n['and']).join([bold(c) for c in conflicting_apps])
if not context.watcher.request_confirmation(title=self.i18n['arch.install.conflict.popup.title'],
body=self.i18n['arch.install.conflict.popup.body'].format(conflict_msg)):
@@ -1975,7 +2036,8 @@ class ArchManager(SoftwareManager):
return False
else: # uninstall conflicts
self._update_progress(context, 75)
to_uninstall = [conflict for conflict in conflicting_apps if conflict != context.name]
names_to_install = context.get_package_names()
to_uninstall = [conflict for conflict in conflicting_apps if conflict not in names_to_install]
self.logger.info("Preparing to uninstall conflicting packages: {}".format(to_uninstall))
@@ -2006,7 +2068,7 @@ class ArchManager(SoftwareManager):
if context.missing_deps:
to_install.extend((d[0] for d in context.missing_deps))
to_install.append(pkgpath)
to_install.extend(pkgpaths)
downloaded = 0
if self._multithreaded_download_enabled(context.config):
@@ -2027,7 +2089,7 @@ class ArchManager(SoftwareManager):
else:
status_handler = None
installed_with_same_name = self.read_installed(disk_loader=context.disk_loader, internet_available=True, names={context.name}).installed
installed_with_same_name = self.read_installed(disk_loader=context.disk_loader, internet_available=True, names=context.get_package_names()).installed
context.watcher.change_substatus(self.i18n['arch.installing.package'].format(bold(context.name)))
installed = self._handle_install_call(context=context, to_install=to_install, status_handler=status_handler)
@@ -2038,7 +2100,7 @@ class ArchManager(SoftwareManager):
self._update_progress(context, 95)
if installed:
context.installed.add(context.name)
context.installed.update(context.get_package_names())
context.installed.update((p for p in to_install if not p.startswith('/')))
if installed_with_same_name:
@@ -2091,7 +2153,7 @@ class ArchManager(SoftwareManager):
def _call_pacman_install(self, context: TransactionContext, to_install: List[str], overwrite_files: bool, status_handler: Optional[object] = None) -> Tuple[bool, str]:
return context.handler.handle_simple(pacman.install_as_process(pkgpaths=to_install,
root_password=context.root_password,
file=context.has_install_file(),
file=context.has_install_files(),
pkgdir=context.project_dir,
overwrite_conflicting_files=overwrite_files),
output_handler=status_handler.handle if status_handler else None)
@@ -2520,6 +2582,17 @@ class ArchManager(SoftwareManager):
only_int=True,
max_width=max_width,
value=local_config['mirrors_sort_limit'] if isinstance(local_config['mirrors_sort_limit'], int) else ''),
new_select(id_='aur_build_only_chosen',
label=self.i18n['arch.config.aur_build_only_chosen'],
tip=self.i18n['arch.config.aur_build_only_chosen.tip'],
opts=[(self.i18n['yes'].capitalize(), True, None),
(self.i18n['no'].capitalize(), False, None),
(self.i18n['ask'].capitalize(), None, None),
],
value=local_config['aur_build_only_chosen'],
max_width=max_width,
type_=SelectViewType.RADIO,
capitalize_label=False),
new_select(label=self.i18n['arch.config.edit_aur_pkgbuild'],
tip=self.i18n['arch.config.edit_aur_pkgbuild.tip'],
id_='edit_aur_pkgbuild',
@@ -2565,6 +2638,7 @@ class ArchManager(SoftwareManager):
config['edit_aur_pkgbuild'] = form_install.get_component('edit_aur_pkgbuild').get_selected()
config['aur_remove_build_dir'] = form_install.get_component('aur_remove_build_dir').get_selected()
config['aur_build_dir'] = form_install.get_component('aur_build_dir').file_path
config['aur_build_only_chosen'] = form_install.get_component('aur_build_only_chosen').get_selected()
if not config['aur_build_dir']:
config['aur_build_dir'] = None
@@ -2932,3 +3006,61 @@ class ArchManager(SoftwareManager):
body=self.i18n['snap.custom_action.setup_snapd.ready.body'],
type_=MessageType.INFO)
return True
def _gen_custom_pkgbuild_if_required(self, context: TransactionContext) -> Optional[str]:
build_only_chosen = context.config.get('aur_build_only_chosen')
pkgs_to_build = aur.map_srcinfo(string=self._read_srcinfo(context), pkgname=None, fields={'pkgname'}).get('pkgname')
if isinstance(pkgs_to_build, str):
pkgs_to_build = {pkgs_to_build}
else:
pkgs_to_build = {*pkgs_to_build}
if build_only_chosen is False:
context.pkgs_to_build = pkgs_to_build
return
# checking if more than one package is mapped for this pkgbuild
if not pkgs_to_build or not isinstance(pkgs_to_build, set) or len(pkgs_to_build) == 1 or context.name not in pkgs_to_build:
context.pkgs_to_build = pkgs_to_build
return
if build_only_chosen is None:
if not context.dependency:
pkgnames = [InputOption(label=n, value=n, read_only=False) for n in pkgs_to_build if n != context.name]
select = MultipleSelectComponent(label='',
options=pkgnames,
default_options={*pkgnames},
max_per_line=1)
if not context.watcher.request_confirmation(title=self.i18n['warning'].capitalize(),
body=self.i18n['arch.aur.sync.several_names.popup.body'].format(bold(context.name)) + ':',
components=[select],
confirmation_label=self.i18n['arch.aur.sync.several_names.popup.bt_only_chosen'].format(context.name),
deny_label=self.i18n['arch.aur.sync.several_names.popup.bt_selected']):
context.pkgs_to_build = {context.name, *select.get_selected_values()}
pkgbuild_path = '{}/PKGBUILD'.format(context.project_dir)
with open(pkgbuild_path) as f:
current_pkgbuild = f.read()
if context.pkgs_to_build:
names = '({})'.format(' '.join(("'{}'".format(p) for p in context.pkgs_to_build)))
else:
names = context.name
context.pkgs_to_build = {context.name}
new_pkgbuild = RE_PKGBUILD_PKGNAME.sub("pkgname={}".format(names), current_pkgbuild)
custom_pkgbuild_path = pkgbuild_path + '_CUSTOM'
with open(custom_pkgbuild_path, 'w+') as f:
f.write(new_pkgbuild)
new_srcinfo = makepkg.gen_srcinfo(context.project_dir, custom_pkgbuild_path)
with open('{}/.SRCINFO'.format(context.project_dir), 'w+') as f:
f.write(new_srcinfo)
return custom_pkgbuild_path