This commit is contained in:
Vinícius Moreira
2021-12-10 16:23:33 -03:00
committed by GitHub
29 changed files with 442 additions and 212 deletions

View File

@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.9.23] 2021-12-10
### Features
- General
- new configuration file `/etc/bauh/gems.forbidden` can be used by system administrators/distribution package managers to disable the management of supported packaging formats globally (more on that [here](https://github.com/vinifmor/bauh#forbidden_gems))
- Arch
- allowing AUR packages to be installed when bauh is launched by the root user [#196](https://github.com/vinifmor/bauh/issues/196)
- it creates a non-root user called **bauh-aur** for building the packages (`useradd` and `runuser` commands must be installed)
### Improvements
- code refactoring (String formatting method)
### Fixes
- Arch
- not updating the view (GUI) status correctly after uninstalling a package whose PKGBUILD file was edited (specifically the **name**)
- missing error handling when hard requirements for optional dependencies cannot be found by pacman
## [0.9.22] 2021-11-30 ## [0.9.22] 2021-11-30
### Improvements ### Improvements
- General - General

View File

@@ -15,22 +15,23 @@ Key features
## Index ## Index
1. [Installation](#installation) 1. [Installation](#installation)
- [Ubuntu-based distros (20.04)](#inst_ubuntu) - [Ubuntu-based distros (20.04)](#inst_ubuntu)
- [Arch-based distros](#inst_arch) - [Arch-based distros](#inst_arch)
2. [Isolated installation](#inst_iso) 2. [Isolated installation](#inst_iso)
3. [Desktop entry / menu shortcut](#desk_entry) 3. [Desktop entry / menu shortcut](#desk_entry)
4. [Autostart: tray mode](#autostart) 4. [Autostart: tray mode](#autostart)
5. [Distribution](#dist) 5. [Distribution](#dist)
6. [Supported types](#types) 6. [Supported types](#types)
- [AppImage](#type_appimage) - [AppImage](#type_appimage)
- [Arch packages/AUR](#type_arch) - [Arch packages/AUR](#type_arch)
- [Flatpak](#type_flatpak) - [Flatpak](#type_flatpak)
- [Snap](#type_snap) - [Snap](#type_snap)
- [Native Web applications](#type_web) - [Native Web applications](#type_web)
7. [General settings](#settings) 7. [General settings](#settings)
8. [Directory structure, caching and logs](#dirs) - [Forbidden packaging formats](#forbidden_gems)
9. [Custom themes](#custom_themes) 8. [Directory structure, caching and logs](#dirs)
9. [Custom themes](#custom_themes)
10. [Tray icons](#tray_icons) 10. [Tray icons](#tray_icons)
11. [CLI (Command Line Interface)](#cli) 11. [CLI (Command Line Interface)](#cli)
12. [Improving performance](#performance) 12. [Improving performance](#performance)
@@ -240,6 +241,9 @@ suggestions:
- If you have AUR added as a repository on you pacman configuration, make sure to disable bauh's support (through the settings described below) - If you have AUR added as a repository on you pacman configuration, make sure to disable bauh's support (through the settings described below)
- AUR package compilation may require additional installed packages to work properly. Some of them are defined on the field `optdepends` of the [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=bauh) - AUR package compilation may require additional installed packages to work properly. Some of them are defined on the field `optdepends` of the [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=bauh)
- for a **root** user the following additional applications must be installed:
- `useradd`: required to create a simple user named **bauh-aur** (since **makepkg** does not allow building packages as the **root** user)
- `runuser`: required to run commands as another user
- **Repository packages currently do not support the following actions: Downgrade and History** - **Repository packages currently do not support the following actions: Downgrade and History**
- If some of your installed packages are not categorized, open a PullRequest to the **bauh-files** repository changing [categories.txt](https://github.com/vinifmor/bauh-files/blob/master/arch/categories.txt) - If some of your installed packages are not categorized, open a PullRequest to the **bauh-files** repository changing [categories.txt](https://github.com/vinifmor/bauh-files/blob/master/arch/categories.txt)
- During bauh initialization a full AUR normalized index is saved at `~/.cache/bauh/arch/aur/index.txt` - During bauh initialization a full AUR normalized index is saved at `~/.cache/bauh/arch/aur/index.txt`
@@ -410,6 +414,16 @@ boot:
load_apps: true # if the installed applications or suggestions should be loaded on the management panel after the initialization process. Default: true. load_apps: true # if the installed applications or suggestions should be loaded on the management panel after the initialization process. Default: true.
``` ```
##### <a name="forbidden_gems">Forbidden packaging formats</a>
- System administrators and package managers of Linux distributions can disable the usage/management of supported packaging formats
by adding their ids to the file `/etc/bauh/gems.forbidden`. This will prevent their management code to be loaded.
- Example (one id per line):
```
arch
appimage
# flatpak # 'sharps' can be used to ignore a given line (comment)
```
#### <a name="dirs">Directory structure, caching and logs</a> #### <a name="dirs">Directory structure, caching and logs</a>
- `~/.config/bauh` (or `/etc/bauh` for **root**): stores configuration files - `~/.config/bauh` (or `/etc/bauh` for **root**): stores configuration files
- `~/.cache/bauh` (or `/var/cache/bauh` for **root**): stores data about your installed applications, databases, indexes, etc. Files are stored here to provide a faster initialization and data recovery. - `~/.cache/bauh` (or `/var/cache/bauh` for **root**): stores data about your installed applications, databases, indexes, etc. Files are stored here to provide a faster initialization and data recovery.

View File

@@ -1,4 +1,4 @@
__version__ = '0.9.22' __version__ = '0.9.23'
__app_name__ = 'bauh' __app_name__ = 'bauh'
import os import os

View File

@@ -4,11 +4,16 @@ from pathlib import Path
from bauh import __app_name__ from bauh import __app_name__
from bauh.api import user from bauh.api import user
def get_temp_dir(username: str) -> str:
return f'/tmp/{__app_name__}@{username}'
CACHE_DIR = f'/var/cache/{__app_name__}' if user.is_root() else f'{Path.home()}/.cache/{__app_name__}' CACHE_DIR = f'/var/cache/{__app_name__}' if user.is_root() else f'{Path.home()}/.cache/{__app_name__}'
CONFIG_DIR = f'/etc/{__app_name__}' if user.is_root() else f'{Path.home()}/.config/{__app_name__}' CONFIG_DIR = f'/etc/{__app_name__}' if user.is_root() else f'{Path.home()}/.config/{__app_name__}'
USER_THEMES_DIR = f'/usr/share/{__app_name__}/themes' if user.is_root() else f'{Path.home()}/.local/share/{__app_name__}/themes' USER_THEMES_DIR = f'/usr/share/{__app_name__}/themes' if user.is_root() else f'{Path.home()}/.local/share/{__app_name__}/themes'
DESKTOP_ENTRIES_DIR = '/usr/share/applications' if user.is_root() else f'{Path.home()}/.local/share/applications' DESKTOP_ENTRIES_DIR = '/usr/share/applications' if user.is_root() else f'{Path.home()}/.local/share/applications'
TEMP_DIR = f'/tmp/{__app_name__}@{getuser()}' TEMP_DIR = get_temp_dir(getuser())
LOGS_DIR = f'{TEMP_DIR}/logs' LOGS_DIR = f'{TEMP_DIR}/logs'
AUTOSTART_DIR = f'/etc/xdg/autostart' if user.is_root() else f'{Path.home()}/.config/autostart' AUTOSTART_DIR = f'/etc/xdg/autostart' if user.is_root() else f'{Path.home()}/.config/autostart'
BINARIES_DIR = f'/usr/local/bin' if user.is_root() else f'{Path.home()}/.local/bin' BINARIES_DIR = f'/usr/local/bin' if user.is_root() else f'{Path.home()}/.local/bin'

View File

@@ -49,7 +49,8 @@ def main():
internet_checker=InternetChecker(offline=False), internet_checker=InternetChecker(offline=False),
root_user=user.is_root()) root_user=user.is_root())
managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config, default_locale=DEFAULT_I18N_KEY) managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config,
default_locale=DEFAULT_I18N_KEY, logger=logger)
cli = CLIManager(GenericSoftwareManager(managers, context=context, config=app_config)) cli = CLIManager(GenericSoftwareManager(managers, context=context, config=app_config))

View File

@@ -67,12 +67,15 @@ class SimpleProcess:
def __init__(self, cmd: List[str], cwd: str = '.', expected_code: int = 0, def __init__(self, cmd: List[str], cwd: str = '.', expected_code: int = 0,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, root_password: str = None, global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, root_password: str = None,
extra_paths: Set[str] = None, error_phrases: Set[str] = None, wrong_error_phrases: Set[str] = None, extra_paths: Set[str] = None, error_phrases: Set[str] = None, wrong_error_phrases: Set[str] = None,
shell: bool = False, shell: bool = False, success_phrases: Set[str] = None, extra_env: Optional[Dict[str, str]] = None,
success_phrases: Set[str] = None, extra_env: Optional[Dict[str, str]] = None): custom_user: Optional[str] = None):
pwdin, final_cmd = None, [] pwdin, final_cmd = None, []
self.shell = shell self.shell = shell
if root_password is not None:
if custom_user:
final_cmd.extend(['runuser', '-u', custom_user, '--'])
elif root_password is not None:
final_cmd.extend(['sudo', '-S']) final_cmd.extend(['sudo', '-S'])
pwdin = self._new(['echo', root_password], cwd, global_interpreter, lang).stdout pwdin = self._new(['echo', root_password], cwd, global_interpreter, lang).stdout
@@ -219,14 +222,10 @@ class ProcessHandler:
def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, print_error: bool = True, def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, print_error: bool = True,
cwd: str = '.', global_interpreter: bool = USE_GLOBAL_INTERPRETER, extra_paths: Set[str] = None) -> str: cwd: str = '.', global_interpreter: bool = USE_GLOBAL_INTERPRETER, extra_paths: Set[str] = None,
custom_user: Optional[str] = None) -> str:
""" """
runs a given command and returns its default output runs a given command and returns its default output
:param cmd:
:param expected_code:
:param ignore_return_code:
:param print_error:
:param global_interpreter
:return: :return:
""" """
args = { args = {
@@ -239,13 +238,14 @@ def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False,
if not print_error: if not print_error:
args["stderr"] = subprocess.DEVNULL args["stderr"] = subprocess.DEVNULL
res = subprocess.run(cmd, **args) final_cmd = f"runuser -u {custom_user} -- {cmd}" if custom_user else cmd
res = subprocess.run(final_cmd, **args)
return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None
def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin = None, def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin = None,
global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG, global_interpreter: bool = USE_GLOBAL_INTERPRETER, lang: str = DEFAULT_LANG,
extra_paths: Set[str] = None) -> subprocess.Popen: extra_paths: Set[str] = None, custom_user: Optional[str] = None) -> subprocess.Popen:
args = { args = {
"stdout": PIPE, "stdout": PIPE,
"stderr": PIPE, "stderr": PIPE,
@@ -255,7 +255,8 @@ def new_subprocess(cmd: List[str], cwd: str = '.', shell: bool = False, stdin =
"stdin": stdin if stdin else subprocess.DEVNULL "stdin": stdin if stdin else subprocess.DEVNULL
} }
return subprocess.Popen(cmd, **args) final_cmd = ['runuser', '-u', custom_user, '--', *cmd] if custom_user else cmd
return subprocess.Popen(final_cmd, **args)
def new_root_subprocess(cmd: List[str], root_password: str, cwd: str = '.', def new_root_subprocess(cmd: List[str], root_password: str, cwd: str = '.',
@@ -302,8 +303,9 @@ def get_human_size_str(size) -> str:
return str(int_size) return str(int_size)
def run(cmd: List[str], success_code: int = 0) -> Tuple[bool, str]: def run(cmd: List[str], success_code: int = 0, custom_user: Optional[str] = None) -> Tuple[bool, str]:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) final_cmd = ['runuser', '-u', custom_user, '--', *cmd] if custom_user else cmd
p = subprocess.run(final_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
return p.returncode == success_code, p.stdout.decode() return p.returncode == success_code, p.stdout.decode()
@@ -327,9 +329,13 @@ def check_enabled_services(*names: str) -> Dict[str, bool]:
return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s} return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s}
def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True, custom_env: Optional[dict] = None, stdin: bool = True) -> Tuple[int, Optional[str]]: def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True, custom_env: Optional[dict] = None,
stdin: bool = True, custom_user: Optional[str] = None) -> Tuple[int, Optional[str]]:
final_cmd = f"runuser -u {custom_user} -- {cmd}" if custom_user else cmd
params = { params = {
'args': cmd.split(' ') if not shell else [cmd], 'args': final_cmd.split(' ') if not shell else [final_cmd],
'stdout': subprocess.PIPE if output else subprocess.DEVNULL, 'stdout': subprocess.PIPE if output else subprocess.DEVNULL,
'stderr': subprocess.STDOUT if output else subprocess.DEVNULL, 'stderr': subprocess.STDOUT if output else subprocess.DEVNULL,
'shell': shell 'shell': shell

View File

@@ -1,11 +1,11 @@
import os import os
from typing import Optional
from bauh import __app_name__ from bauh import __app_name__
from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR from bauh.api.paths import CONFIG_DIR, TEMP_DIR, CACHE_DIR, get_temp_dir
from bauh.commons import resource from bauh.commons import resource
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
BUILD_DIR = f'{TEMP_DIR}/arch'
ARCH_CACHE_DIR = f'{CACHE_DIR}/arch' ARCH_CACHE_DIR = f'{CACHE_DIR}/arch'
CATEGORIES_FILE_PATH = f'{ARCH_CACHE_DIR}/categories.txt' CATEGORIES_FILE_PATH = f'{ARCH_CACHE_DIR}/categories.txt'
URL_CATEGORIES_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/categories.txt' URL_CATEGORIES_FILE = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/arch/categories.txt'
@@ -21,6 +21,10 @@ EDITABLE_PKGBUILDS_FILE = f'{ARCH_CONFIG_DIR}/aur/editable_pkgbuilds.txt'
IGNORED_REBUILD_CHECK_FILE = f'{ARCH_CONFIG_DIR}/aur/ignored_rebuild_check.txt' IGNORED_REBUILD_CHECK_FILE = f'{ARCH_CONFIG_DIR}/aur/ignored_rebuild_check.txt'
def get_pkgbuild_dir(user: Optional[str] = None) -> str:
return f'{get_temp_dir(user) if user else TEMP_DIR}/arch'
def get_icon_path() -> str: def get_icon_path() -> str:
return resource.get_path('img/arch.svg', ROOT_DIR) return resource.get_path('img/arch.svg', ROOT_DIR)

View File

@@ -42,10 +42,6 @@ KNOWN_LIST_FIELDS = ('validpgpkeys',
'conflicts') 'conflicts')
def map_pkgbuild(pkgbuild: str) -> dict:
return {attr: val.replace('"', '').replace("'", '').replace('(', '').replace(')', '') for attr, val in re.findall(r'\n(\w+)=(.+)', pkgbuild)}
def map_srcinfo(string: str, pkgname: Optional[str], fields: Set[str] = None) -> dict: def map_srcinfo(string: str, pkgname: Optional[str], fields: Set[str] = None) -> dict:
subinfos, subinfo = [], {} subinfos, subinfo = [], {}
@@ -260,3 +256,4 @@ class AURClient:
def is_supported(arch_config: dict) -> bool: def is_supported(arch_config: dict) -> bool:
return arch_config['aur'] and git.is_installed() return arch_config['aur'] and git.is_installed()

View File

@@ -1,16 +1,15 @@
from pathlib import Path from typing import Optional
from bauh.commons.config import YAMLConfigManager from bauh.commons.config import YAMLConfigManager
from bauh.gems.arch import CONFIG_FILE, BUILD_DIR from bauh.gems.arch import CONFIG_FILE, get_pkgbuild_dir
def get_build_dir(arch_config: dict) -> str: def get_build_dir(arch_config: dict, user: Optional[str]) -> str:
build_dir = arch_config.get('aur_build_dir') build_dir = arch_config.get('aur_build_dir')
if not build_dir: if not build_dir:
build_dir = BUILD_DIR build_dir = get_pkgbuild_dir(user)
Path(build_dir).mkdir(parents=True, exist_ok=True)
return build_dir return build_dir

View File

@@ -10,12 +10,14 @@ import traceback
from datetime import datetime from datetime import datetime
from math import floor from math import floor
from pathlib import Path from pathlib import Path
from pwd import getpwnam
from threading import Thread from threading import Thread
from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection
import requests import requests
from dateutil.parser import parse as parse_date from dateutil.parser import parse as parse_date
from bauh import __app_name__
from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \
TransactionResult, SoftwareAction TransactionResult, SoftwareAction
from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.disk import DiskCacheLoader
@@ -25,20 +27,19 @@ from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePacka
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \ ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \
FileChooserComponent, TextComponent FileChooserComponent, TextComponent
from bauh.api.paths import TEMP_DIR
from bauh.api.exception import NoInternetException from bauh.api.exception import NoInternetException
from bauh.api.paths import TEMP_DIR
from bauh.commons import system from bauh.commons import system
from bauh.api import user
from bauh.commons.boot import CreateConfigFile from bauh.commons.boot import CreateConfigFile
from bauh.commons.category import CategoriesDownloader from bauh.commons.category import CategoriesDownloader
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess
from bauh.commons.util import datetime_as_milis from bauh.commons.util import datetime_as_milis
from bauh.commons.view_utils import new_select from bauh.commons.view_utils import new_select
from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \ from bauh.gems.arch import aur, pacman, message, confirmation, disk, git, \
gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \
get_icon_path, database, mirrors, sorting, cpu_manager, UPDATES_IGNORED_FILE, \ get_icon_path, database, mirrors, sorting, cpu_manager, UPDATES_IGNORED_FILE, \
ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR, rebuild_detector ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, rebuild_detector, makepkg, sshell
from bauh.gems.arch.aur import AURClient from bauh.gems.arch.aur import AURClient
from bauh.gems.arch.config import get_build_dir, ArchConfigManager from bauh.gems.arch.config import get_build_dir, ArchConfigManager
from bauh.gems.arch.dependencies import DependenciesAnalyser from bauh.gems.arch.dependencies import DependenciesAnalyser
@@ -48,6 +49,7 @@ from bauh.gems.arch.mapper import AURDataMapper
from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.model import ArchPackage
from bauh.gems.arch.output import TransactionStatusHandler from bauh.gems.arch.output import TransactionStatusHandler
from bauh.gems.arch.pacman import RE_DEP_OPERATORS from bauh.gems.arch.pacman import RE_DEP_OPERATORS
from bauh.gems.arch.proc_util import write_as_user
from bauh.gems.arch.updates import UpdatesSummarizer from bauh.gems.arch.updates import UpdatesSummarizer
from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, RefreshMirrors, \ from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer, RefreshMirrors, \
SyncDatabases SyncDatabases
@@ -247,6 +249,7 @@ class ArchManager(SoftwareManager):
self.index_aur = None self.index_aur = None
self.re_file_conflict = re.compile(r'[\w\d\-_.]+:') self.re_file_conflict = re.compile(r'[\w\d\-_.]+:')
self.disk_cache_updater = disk_cache_updater self.disk_cache_updater = disk_cache_updater
self.pkgbuilder_user: Optional[str] = f'{__app_name__}-aur' if context.root_user else None
@staticmethod @staticmethod
def get_aur_semantic_search_map() -> Dict[str, str]: def get_aur_semantic_search_map() -> Dict[str, str]:
@@ -673,32 +676,39 @@ class ArchManager(SoftwareManager):
return SearchResult(pkgs, None, len(pkgs)) return SearchResult(pkgs, None, len(pkgs))
def _downgrade_aur_pkg(self, context: TransactionContext): def _downgrade_aur_pkg(self, context: TransactionContext) -> bool:
if not self.add_package_builder_user(context.handler):
return False
if context.commit: if context.commit:
self.logger.info("Package '{}' current commit {}".format(context.name, context.commit)) self.logger.info("Package '{}' current commit {}".format(context.name, context.commit))
else: else:
self.logger.warning("Package '{}' has no commit associated with it. Downgrading will only compare versions.".format(context.name)) self.logger.warning("Package '{}' has no commit associated with it. Downgrading will only compare versions.".format(context.name))
context.build_dir = '{}/build_{}'.format(get_build_dir(context.config), int(time.time())) context.build_dir = f'{get_build_dir(context.config, self.pkgbuilder_user)}/build_{int(time.time())}'
try: try:
if not os.path.exists(context.build_dir): if not os.path.exists(context.build_dir):
build_dir = context.handler.handle(SystemProcess(new_subprocess(['mkdir', '-p', context.build_dir]))) build_dir, build_dir_error = sshell.mkdir(dir_path=context.build_dir, custom_user=self.pkgbuilder_user)
if build_dir: if not build_dir:
context.watcher.print(build_dir_error)
else:
context.handler.watcher.change_progress(10) context.handler.watcher.change_progress(10)
base_name = context.get_base_name() base_name = context.get_base_name()
context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(context.name))) context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(context.name)))
cloned, _ = context.handler.handle_simple(git.clone_as_process(url=URL_GIT.format(base_name), cwd=context.build_dir)) clone_dir = f'{context.build_dir}/{base_name}'
cloned, _ = context.handler.handle_simple(git.clone(url=URL_GIT.format(base_name),
target_dir=clone_dir,
custom_user=self.pkgbuilder_user))
context.watcher.change_progress(30) context.watcher.change_progress(30)
if cloned: if cloned:
context.watcher.change_substatus(self.i18n['arch.downgrade.reading_commits']) context.watcher.change_substatus(self.i18n['arch.downgrade.reading_commits'])
clone_path = '{}/{}'.format(context.build_dir, base_name) context.project_dir = clone_dir
context.project_dir = clone_path srcinfo_path = f'{clone_dir}/.SRCINFO'
srcinfo_path = '{}/.SRCINFO'.format(clone_path)
logs = git.log_shas_and_timestamps(clone_path) logs = git.log_shas_and_timestamps(clone_dir)
context.watcher.change_progress(40) context.watcher.change_progress(40)
if not logs or len(logs) == 1: if not logs or len(logs) == 1:
@@ -719,7 +729,7 @@ class ArchManager(SoftwareManager):
self.logger.warning("Could not find '{}' target commit to revert to".format(context.name)) self.logger.warning("Could not find '{}' target commit to revert to".format(context.name))
else: else:
context.watcher.change_substatus(self.i18n['arch.downgrade.version_found']) context.watcher.change_substatus(self.i18n['arch.downgrade.version_found'])
checkout_proc = new_subprocess(['git', 'checkout', target_commit], cwd=clone_path) checkout_proc = new_subprocess(['git', 'checkout', target_commit], cwd=clone_dir, custom_user=self.pkgbuilder_user)
if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)): if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)):
context.watcher.print("Could not rollback to current version's commit") context.watcher.print("Could not rollback to current version's commit")
return False return False
@@ -738,7 +748,7 @@ class ArchManager(SoftwareManager):
with open(srcinfo_path) as f: with open(srcinfo_path) as f:
pkgsrc = aur.map_srcinfo(string=f.read(), pkgname=context.name, fields=srcfields) pkgsrc = aur.map_srcinfo(string=f.read(), pkgname=context.name, fields=srcfields)
reset_proc = new_subprocess(['git', 'reset', '--hard', commit], cwd=clone_path) reset_proc = new_subprocess(['git', 'reset', '--hard', commit], cwd=clone_dir, custom_user=self.pkgbuilder_user)
if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)): if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)):
context.handler.watcher.print('Could not downgrade anymore. Aborting...') context.handler.watcher.print('Could not downgrade anymore. Aborting...')
return False return False
@@ -752,18 +762,18 @@ class ArchManager(SoftwareManager):
if commit_found: if commit_found:
context.watcher.change_substatus(self.i18n['arch.downgrade.version_found']) context.watcher.change_substatus(self.i18n['arch.downgrade.version_found'])
checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_path) checkout_proc = new_subprocess(['git', 'checkout', commit_found], cwd=clone_dir, custom_user=self.pkgbuilder_user)
if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)): if not context.handler.handle(SystemProcess(checkout_proc, check_error_output=False)):
context.watcher.print("Could not rollback to current version's commit") context.watcher.print("Could not rollback to current version's commit")
return False return False
reset_proc = new_subprocess(['git', 'reset', '--hard', commit_found], cwd=clone_path) reset_proc = new_subprocess(['git', 'reset', '--hard', commit_found], cwd=clone_dir, custom_user=self.pkgbuilder_user)
if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)): if not context.handler.handle(SystemProcess(reset_proc, check_error_output=False)):
context.watcher.print("Could not downgrade to previous commit of '{}'. Aborting...".format(commit_found)) context.watcher.print("Could not downgrade to previous commit of '{}'. Aborting...".format(commit_found))
return False return False
break break
elif current_version == context.get_version(): # current version found: elif current_version == context.get_version():
commit_found, commit_date = commit, date commit_found, commit_date = commit, date
context.watcher.change_substatus(self.i18n['arch.downgrade.install_older']) context.watcher.change_substatus(self.i18n['arch.downgrade.install_older'])
@@ -825,10 +835,11 @@ class ArchManager(SoftwareManager):
return self._install(context) return self._install(context)
def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool:
self.aur_client.clean_caches() if not self.check_action_allowed(pkg, watcher):
if not self._check_action_allowed(pkg, watcher):
return False return False
self.aur_client.clean_caches()
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
if self._is_database_locked(handler, root_password): if self._is_database_locked(handler, root_password):
@@ -857,14 +868,6 @@ class ArchManager(SoftwareManager):
if os.path.exists(pkg.get_disk_cache_path()): if os.path.exists(pkg.get_disk_cache_path()):
shutil.rmtree(pkg.get_disk_cache_path()) shutil.rmtree(pkg.get_disk_cache_path())
def _check_action_allowed(self, pkg: ArchPackage, watcher: ProcessWatcher) -> bool:
if user.is_root() and pkg.repository == 'aur':
watcher.show_message(title=self.i18n['arch.install.aur.root_error.title'],
body=self.i18n['arch.install.aur.root_error.body'],
type_=MessageType.ERROR)
return False
return True
def _is_database_locked(self, handler: ProcessHandler, root_password: str) -> bool: def _is_database_locked(self, handler: ProcessHandler, root_password: str) -> bool:
if os.path.exists('/var/lib/pacman/db.lck'): if os.path.exists('/var/lib/pacman/db.lck'):
handler.watcher.print('pacman database is locked') handler.watcher.print('pacman database is locked')
@@ -1155,16 +1158,13 @@ class ArchManager(SoftwareManager):
pkg_sizes[req.pkg.name] = req.required_size pkg_sizes[req.pkg.name] = req.required_size
if aur_pkgs and not self._check_action_allowed(aur_pkgs[0], watcher):
return False
arch_config = self.configman.get_config() arch_config = self.configman.get_config()
aur_supported = bool(aur_pkgs) or aur.is_supported(arch_config) aur_supported = bool(aur_pkgs) or aur.is_supported(arch_config)
self._sync_databases(arch_config=arch_config, aur_supported=aur_supported, self._sync_databases(arch_config=arch_config, aur_supported=aur_supported,
root_password=root_password, handler=handler) root_password=root_password, handler=handler)
if repo_pkgs: if repo_pkgs and self.check_action_allowed(repo_pkgs[0], watcher):
if not self._upgrade_repo_pkgs(to_upgrade=[p.name for p in repo_pkgs], 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, to_remove={r.pkg.name for r in requirements.to_remove} if requirements.to_remove else None,
handler=handler, handler=handler,
@@ -1177,7 +1177,7 @@ class ArchManager(SoftwareManager):
elif requirements.to_remove and not self._remove_transaction_packages({r.pkg.name for r in requirements.to_remove}, handler, root_password): elif requirements.to_remove and not self._remove_transaction_packages({r.pkg.name for r in requirements.to_remove}, handler, root_password):
return False return False
if aur_pkgs: if aur_pkgs and self.check_action_allowed(aur_pkgs[0], watcher) and self.add_package_builder_user(handler):
watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs'])) watcher.change_status('{}...'.format(self.i18n['arch.upgrade.upgrade_aur_pkgs']))
self.logger.info("Retrieving the 'last_modified' field for each package to upgrade") self.logger.info("Retrieving the 'last_modified' field for each package to upgrade")
@@ -1457,7 +1457,16 @@ class ArchManager(SoftwareManager):
names={pkg.name}, names={pkg.name},
disk_loader=disk_loader) # to be able to return all uninstalled packages disk_loader=disk_loader) # to be able to return all uninstalled packages
if success: if success:
return TransactionResult(success=True, installed=None, removed=[*removed.values()] if removed else []) removed_list = []
main_removed = removed.get(pkg.name)
if main_removed:
pkg.installed = False
pkg.url_download = main_removed.url_download # otherwise uninstalled AUR packages cannot be reinstalled on the same view
removed_list.append(pkg)
removed_list.extend((inst for name, inst in removed.items() if name != pkg.name))
return TransactionResult(success=not pkg.installed, installed=None, removed=removed_list)
else: else:
return TransactionResult.fail() return TransactionResult.fail()
@@ -1581,21 +1590,21 @@ class ArchManager(SoftwareManager):
self.logger.warning("Package '{}' has no commit associated with it. Current history status may not be correct.".format(pkg.name)) self.logger.warning("Package '{}' has no commit associated with it. Current history status may not be correct.".format(pkg.name))
arch_config = self.configman.get_config() arch_config = self.configman.get_config()
temp_dir = '{}/build_{}'.format(get_build_dir(arch_config), int(time.time())) temp_dir = f'{get_build_dir(arch_config, self.pkgbuilder_user)}/build_{int(time.time())}'
try: try:
Path(temp_dir).mkdir(parents=True) Path(temp_dir).mkdir(parents=True)
base_name = pkg.get_base_name() base_name = pkg.get_base_name()
run_cmd('git clone ' + URL_GIT.format(base_name), print_error=False, cwd=temp_dir) run_cmd('git clone ' + URL_GIT.format(base_name), print_error=False, cwd=temp_dir)
clone_path = '{}/{}'.format(temp_dir, base_name) clone_dir = f'{temp_dir}/{base_name}'
srcinfo_path = '{}/.SRCINFO'.format(clone_path) srcinfo_path = f'{clone_dir}/.SRCINFO'
if not os.path.exists(srcinfo_path): if not os.path.exists(srcinfo_path):
return PackageHistory.empyt(pkg) return PackageHistory.empyt(pkg)
logs = git.log_shas_and_timestamps(clone_path) logs = git.log_shas_and_timestamps(clone_dir)
if logs: if logs:
srcfields = {'epoch', 'pkgver', 'pkgrel'} srcfields = {'epoch', 'pkgver', 'pkgrel'}
@@ -1622,7 +1631,7 @@ class ArchManager(SoftwareManager):
'3_date': datetime.fromtimestamp(timestamp)}) # the number prefix is to ensure the rendering order '3_date': datetime.fromtimestamp(timestamp)}) # the number prefix is to ensure the rendering order
if idx + 1 < len(logs): if idx + 1 < len(logs):
if not run_cmd('git reset --hard ' + logs[idx + 1][0], cwd=clone_path): if not run_cmd('git reset --hard ' + logs[idx + 1][0], cwd=clone_dir):
break break
return PackageHistory(pkg=pkg, history=history, pkg_status_idx=status_idx) return PackageHistory(pkg=pkg, history=history, pkg_status_idx=status_idx)
@@ -1738,7 +1747,7 @@ class ArchManager(SoftwareManager):
context.restabilish_progress() context.restabilish_progress()
return res return res
def _install_deps(self, context: TransactionContext, deps: List[Tuple[str, str]]) -> Iterable[str]: def _install_deps(self, context: TransactionContext, deps: List[Tuple[str, str]]) -> Optional[Iterable[str]]:
progress_increment = int(100 / len(deps)) progress_increment = int(100 / len(deps))
progress = 0 progress = 0
self._update_progress(context, 1) self._update_progress(context, 1)
@@ -1785,7 +1794,7 @@ class ArchManager(SoftwareManager):
pkg_sizes = pacman.map_download_sizes(repo_dep_names) pkg_sizes = pacman.map_download_sizes(repo_dep_names)
downloaded = self._download_packages(repo_dep_names, context.handler, context.root_password, pkg_sizes, multithreaded=True) downloaded = self._download_packages(repo_dep_names, context.handler, context.root_password, pkg_sizes, multithreaded=True)
except ArchDownloadException: except ArchDownloadException:
return False return None
status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names={*repo_dep_names}, status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names={*repo_dep_names},
logger=self.logger, percentage=len(repo_deps) > 1, downloading=downloaded) logger=self.logger, percentage=len(repo_deps) > 1, downloading=downloaded)
@@ -1827,7 +1836,8 @@ class ArchManager(SoftwareManager):
return pkg_repos return pkg_repos
def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessWatcher) -> bool: def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessWatcher) -> bool:
if self.context.file_downloader.is_multithreaded(): # TODO: multi-threaded download client cannot be run as another user at the moment
if not self.context.root_user and self.context.file_downloader.is_multithreaded():
with open('{}/.SRCINFO'.format(project_dir)) as f: with open('{}/.SRCINFO'.format(project_dir)) as f:
srcinfo = aur.map_srcinfo(string=f.read(), pkgname=pkgname) srcinfo = aur.map_srcinfo(string=f.read(), pkgname=pkgname)
@@ -1839,7 +1849,7 @@ class ArchManager(SoftwareManager):
continue continue
else: else:
for f in srcinfo[attr]: for f in srcinfo[attr]:
if RE_PRE_DOWNLOAD_WL_PROTOCOLS.match(f) and not RE_PRE_DOWNLOAD_BL_EXT.match(f): if RE_PRE_DOWNLOAD_WL_PROTOCOLS.match(f) and not RE_PRE_DOWNLOAD_BL_EXT.match(f):
pre_download_files.append(f) pre_download_files.append(f)
if pre_download_files: if pre_download_files:
@@ -1872,10 +1882,11 @@ class ArchManager(SoftwareManager):
deny_button=False) deny_button=False)
if pkgbuild_input.get_value() != pkgbuild: if pkgbuild_input.get_value() != pkgbuild:
with open(pkgbuild_path, 'w+') as f: if not write_as_user(content=pkgbuild_input.get_value(), file_path=pkgbuild_path, user=self.pkgbuilder_user):
f.write(pkgbuild_input.get_value()) return False
return makepkg.update_srcinfo('/'.join(pkgbuild_path.split('/')[0:-1])) return makepkg.update_srcinfo(project_dir='/'.join(pkgbuild_path.split('/')[0:-1]),
custom_user=self.pkgbuilder_user)
return False return False
@@ -1897,9 +1908,10 @@ class ArchManager(SoftwareManager):
if self._ask_for_pkgbuild_edition(pkgname=context.name, if self._ask_for_pkgbuild_edition(pkgname=context.name,
arch_config=context.config, arch_config=context.config,
watcher=context.watcher, watcher=context.watcher,
pkgbuild_path='{}/PKGBUILD'.format(context.project_dir)): pkgbuild_path=f'{context.project_dir}/PKGBUILD'):
context.pkgbuild_edited = True context.pkgbuild_edited = True
srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(context.project_dir), pkgname=context.name) srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(build_dir=context.project_dir, custom_user=self.pkgbuilder_user),
pkgname=context.name)
if srcinfo: if srcinfo:
context.name = srcinfo['pkgname'] context.name = srcinfo['pkgname']
@@ -1915,12 +1927,14 @@ class ArchManager(SoftwareManager):
setattr(context.pkg, pkgattr, srcinfo.get(srcattr, getattr(context.pkg, pkgattr))) setattr(context.pkg, pkgattr, srcinfo.get(srcattr, getattr(context.pkg, pkgattr)))
def _read_srcinfo(self, context: TransactionContext) -> str: def _read_srcinfo(self, context: TransactionContext) -> str:
src_path = '{}/.SRCINFO'.format(context.project_dir) src_path = f'{context.project_dir}/.SRCINFO'
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: if not os.path.exists(src_path):
f.write(srcinfo) srcinfo = makepkg.gen_srcinfo(build_dir=context.project_dir,
custom_pkgbuild_path=context.custom_pkgbuild_path,
custom_user=self.pkgbuilder_user)
write_as_user(content=srcinfo, file_path=src_path, user=self.pkgbuilder_user)
else: else:
with open(src_path) as f: with open(src_path) as f:
srcinfo = f.read() srcinfo = f.read()
@@ -1947,10 +1961,11 @@ class ArchManager(SoftwareManager):
cpus_changed, cpu_prev_governors = cpu_manager.set_all_cpus_to('performance', context.root_password, self.logger) cpus_changed, cpu_prev_governors = cpu_manager.set_all_cpus_to('performance', context.root_password, self.logger)
try: try:
pkgbuilt, output = makepkg.make(pkgdir=context.project_dir, pkgbuilt, output = makepkg.build(pkgdir=context.project_dir,
optimize=optimize, optimize=optimize,
handler=context.handler, handler=context.handler,
custom_pkgbuild=context.custom_pkgbuild_path) custom_pkgbuild=context.custom_pkgbuild_path,
custom_user=self.pkgbuilder_user)
finally: finally:
if cpus_changed and cpu_prev_governors: if cpus_changed and cpu_prev_governors:
self.logger.info("Restoring CPU governors") self.logger.info("Restoring CPU governors")
@@ -1998,7 +2013,10 @@ class ArchManager(SoftwareManager):
def __fill_aur_output_files(self, context: TransactionContext): def __fill_aur_output_files(self, context: TransactionContext):
self.logger.info("Determining output files of '{}'".format(context.name)) self.logger.info("Determining output files of '{}'".format(context.name))
context.watcher.change_substatus(self.i18n['arch.aur.build.list_output']) 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)}
output_files = {f for f in makepkg.list_output_files(project_dir=context.project_dir,
custom_pkgbuild_path=context.custom_pkgbuild_path,
custom_user=self.pkgbuilder_user) if os.path.isfile(f)}
if output_files: if output_files:
context.install_files = output_files context.install_files = output_files
@@ -2013,7 +2031,8 @@ class ArchManager(SoftwareManager):
file_to_install = gen_file[0] file_to_install = gen_file[0]
if len(gen_file) > 1: if len(gen_file) > 1:
srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(context.project_dir), pkgname=context.name) srcinfo = aur.map_srcinfo(string=makepkg.gen_srcinfo(build_dir=context.project_dir, custom_user=self.pkgbuilder_user),
pkgname=context.name)
pkgver = '-{}'.format(srcinfo['pkgver']) if srcinfo.get('pkgver') else '' pkgver = '-{}'.format(srcinfo['pkgver']) if srcinfo.get('pkgver') else ''
pkgrel = '-{}'.format(srcinfo['pkgrel']) if srcinfo.get('pkgrel') else '' pkgrel = '-{}'.format(srcinfo['pkgrel']) if srcinfo.get('pkgrel') else ''
arch = '-{}'.format(srcinfo['arch']) if srcinfo.get('arch') else '' arch = '-{}'.format(srcinfo['arch']) if srcinfo.get('arch') else ''
@@ -2134,11 +2153,12 @@ class ArchManager(SoftwareManager):
if not handled_deps: if not handled_deps:
return False return False
check_res = makepkg.check(context.project_dir, check_res = makepkg.check(project_dir=context.project_dir,
optimize=bool(context.config['optimize']), optimize=bool(context.config['optimize']),
missing_deps=False, missing_deps=False,
handler=context.handler, handler=context.handler,
custom_pkgbuild=context.custom_pkgbuild_path) custom_pkgbuild=context.custom_pkgbuild_path,
custom_user=self.pkgbuilder_user)
if check_res: if check_res:
if check_res.get('gpg_key'): if check_res.get('gpg_key'):
@@ -2502,23 +2522,30 @@ class ArchManager(SoftwareManager):
return False return False
def _install_from_aur(self, context: TransactionContext) -> bool: def _install_from_aur(self, context: TransactionContext) -> bool:
if not context.dependency and not self.add_package_builder_user(context.handler):
return False
self._optimize_makepkg(context.config, context.watcher) self._optimize_makepkg(context.config, context.watcher)
context.build_dir = '{}/build_{}'.format(get_build_dir(context.config), int(time.time())) context.build_dir = f'{get_build_dir(context.config, self.pkgbuilder_user)}/build_{int(time.time())}'
try: try:
if not os.path.exists(context.build_dir): if not os.path.exists(context.build_dir):
build_dir = context.handler.handle(SystemProcess(new_subprocess(['mkdir', '-p', context.build_dir]))) build_dir, build_dir_error = sshell.mkdir(dir_path=context.build_dir, custom_user=self.pkgbuilder_user)
self._update_progress(context, 10) self._update_progress(context, 10)
if build_dir: if not build_dir:
context.watcher.print(build_dir_error)
else:
base_name = context.get_base_name() base_name = context.get_base_name()
context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(base_name))) context.watcher.change_substatus(self.i18n['arch.clone'].format(bold(base_name)))
cloned = context.handler.handle_simple(git.clone_as_process(url=URL_GIT.format(base_name), cwd=context.build_dir, depth=1)) clone_dir = f'{context.build_dir}/{base_name}'
cloned = context.handler.handle_simple(git.clone(url=URL_GIT.format(base_name), target_dir=clone_dir,
depth=1, custom_user=self.pkgbuilder_user))
if cloned: if cloned:
self._update_progress(context, 40) self._update_progress(context, 40)
context.project_dir = '{}/{}'.format(context.build_dir, base_name) context.project_dir = clone_dir
return self._build(context) return self._build(context)
finally: finally:
if os.path.exists(context.build_dir) and context.config['aur_remove_build_dir']: if os.path.exists(context.build_dir) and context.config['aur_remove_build_dir']:
@@ -2544,10 +2571,10 @@ class ArchManager(SoftwareManager):
ArchCompilationOptimizer(i18n=self.i18n, logger=self.context.logger, taskman=TaskManager()).optimize() ArchCompilationOptimizer(i18n=self.i18n, logger=self.context.logger, taskman=TaskManager()).optimize()
def install(self, pkg: ArchPackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult: def install(self, pkg: ArchPackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult:
self.aur_client.clean_caches() if not self.check_action_allowed(pkg, watcher):
return TransactionResult.fail()
if not self._check_action_allowed(pkg, watcher): self.aur_client.clean_caches()
return TransactionResult(success=False, installed=[], removed=[])
handler = ProcessHandler(watcher) if not context else context.handler handler = ProcessHandler(watcher) if not context else context.handler
@@ -2567,11 +2594,11 @@ class ArchManager(SoftwareManager):
root_password=root_password, handler=handler) root_password=root_password, handler=handler)
if pkg.repository == 'aur': if pkg.repository == 'aur':
res = self._install_from_aur(install_context) pkg_installed = self._install_from_aur(install_context)
else: else:
res = self._install_from_repository(install_context) pkg_installed = self._install_from_repository(install_context)
if res: if pkg_installed:
pkg.name = install_context.name # changes the package name in case the PKGBUILD was edited pkg.name = install_context.name # changes the package name in case the PKGBUILD was edited
if os.path.exists(pkg.get_disk_data_path()): if os.path.exists(pkg.get_disk_data_path()):
@@ -2589,7 +2616,7 @@ class ArchManager(SoftwareManager):
installed = [] installed = []
if res and disk_loader and install_context.installed: if pkg_installed and disk_loader and install_context.installed:
installed.append(pkg) installed.append(pkg)
installed_to_load = [] installed_to_load = []
@@ -2610,7 +2637,7 @@ class ArchManager(SoftwareManager):
self.logger.warning("Could not load all installed packages. Missing: {}".format(missing)) self.logger.warning("Could not load all installed packages. Missing: {}".format(missing))
removed = [*install_context.removed.values()] if install_context.removed else [] removed = [*install_context.removed.values()] if install_context.removed else []
return TransactionResult(success=res, installed=installed, removed=removed) return TransactionResult(success=pkg_installed, installed=installed, removed=removed)
def _install_from_repository(self, context: TransactionContext) -> bool: def _install_from_repository(self, context: TransactionContext) -> bool:
try: try:
@@ -2936,7 +2963,7 @@ class ArchManager(SoftwareManager):
capitalize_label=False), capitalize_label=False),
FileChooserComponent(id_='aur_build_dir', FileChooserComponent(id_='aur_build_dir',
label=self.i18n['arch.config.aur_build_dir'], label=self.i18n['arch.config.aur_build_dir'],
tooltip=self.i18n['arch.config.aur_build_dir.tip'].format(BUILD_DIR), tooltip=self.i18n['arch.config.aur_build_dir.tip'].format(get_build_dir(arch_config, self.pkgbuilder_user)),
max_width=max_width, max_width=max_width,
file_path=arch_config['aur_build_dir'], file_path=arch_config['aur_build_dir'],
capitalize_label=False, capitalize_label=False,
@@ -3397,7 +3424,8 @@ class ArchManager(SoftwareManager):
deny_label=self.i18n['arch.aur.sync.several_names.popup.bt_selected']): deny_label=self.i18n['arch.aur.sync.several_names.popup.bt_selected']):
context.pkgs_to_build = {context.name, *select.get_selected_values()} context.pkgs_to_build = {context.name, *select.get_selected_values()}
pkgbuild_path = '{}/PKGBUILD'.format(context.project_dir) pkgbuild_path = f'{context.project_dir}/PKGBUILD'
with open(pkgbuild_path) as f: with open(pkgbuild_path) as f:
current_pkgbuild = f.read() current_pkgbuild = f.read()
@@ -3407,16 +3435,22 @@ class ArchManager(SoftwareManager):
names = context.name names = context.name
context.pkgs_to_build = {context.name} context.pkgs_to_build = {context.name}
new_pkgbuild = RE_PKGBUILD_PKGNAME.sub("pkgname={}".format(names), current_pkgbuild) new_pkgbuild = RE_PKGBUILD_PKGNAME.sub(f"pkgname={names}", current_pkgbuild)
custom_pkgbuild_path = pkgbuild_path + '_CUSTOM' custom_pkgbuild_path = f'{pkgbuild_path}_CUSTOM'
with open(custom_pkgbuild_path, 'w+') as f: if not write_as_user(content=new_pkgbuild,
f.write(new_pkgbuild) file_path=custom_pkgbuild_path,
user=self.pkgbuilder_user):
self.logger.error(f"Could not write edited PKGBUILD to '{custom_pkgbuild_path}'")
return
new_srcinfo = makepkg.gen_srcinfo(context.project_dir, custom_pkgbuild_path) new_srcinfo = makepkg.gen_srcinfo(build_dir=context.project_dir,
custom_pkgbuild_path=custom_pkgbuild_path,
custom_user=self.pkgbuilder_user)
with open('{}/.SRCINFO'.format(context.project_dir), 'w+') as f: srcinfo_path = f'{context.project_dir}/.SRCINFO'
f.write(new_srcinfo) if not write_as_user(content=new_srcinfo, file_path=srcinfo_path, user=self.pkgbuilder_user):
self.logger.warning(f"Could not write the updated .SRCINFO content to '{srcinfo_path}'")
return custom_pkgbuild_path return custom_pkgbuild_path
@@ -3449,6 +3483,8 @@ class ArchManager(SoftwareManager):
except PackageInHoldException: except PackageInHoldException:
self.logger.warning("There is a requirement in hold for opt dep '{}'".format(p)) self.logger.warning("There is a requirement in hold for opt dep '{}'".format(p))
continue continue
except PackageNotFoundException:
self.logger.warning(f"No hard requirements found for optional {p}. Reason: package not found")
return res return res
@@ -3499,3 +3535,47 @@ class ArchManager(SoftwareManager):
pkg.update_state() pkg.update_state()
return True return True
def check_action_allowed(self, pkg: ArchPackage, watcher: Optional[ProcessWatcher]) -> bool:
if self.context.root_user and pkg.repository == 'aur':
if not shutil.which('useradd'):
if watcher:
watcher.show_message(title=self.i18n['error'].capitalize(),
type_=MessageType.ERROR,
body=self.i18n['arch.aur.error.missing_root_dep'].format(dep=bold('useradd'),
aur=bold('AUR'),
root=bold('root')))
return False
if not shutil.which('runuser'):
if watcher:
watcher.show_message(title=self.i18n['error'].capitalize(),
type_=MessageType.ERROR,
body=self.i18n['arch.aur.error.missing_root_dep'].format(dep=bold('runuser'),
aur=bold('AUR'),
root=bold('root')))
return False
return True
def add_package_builder_user(self, handler: ProcessHandler) -> bool:
if self.context.root_user and self.pkgbuilder_user:
try:
getpwnam(self.pkgbuilder_user)
return True
except KeyError:
self.logger.warning(f"Package builder user '{self.pkgbuilder_user}' does not exist")
self.logger.info(f"Adding the package builder user '{self.pkgbuilder_user}'")
added, output = handler.handle_simple(SimpleProcess(cmd=['useradd', self.pkgbuilder_user], shell=True))
if not added:
output_log = "Command output: {}".format(output.replace('\n', ' ') if output else '(no output)')
self.logger.error(f"Could not add the package builder user '{self.pkgbuilder_user}'. {output_log}")
handler.watcher.show_message(title=self.i18n['error'].capitalize(),
type_=MessageType.ERROR,
body=self.i18n['arch.aur.error.add_builder_user'].format(user=bold(self.pkgbuilder_user),
aur=bold('AUR')))
return added
return True

View File

@@ -53,10 +53,13 @@ def log_shas_and_timestamps(repo_path: str) -> Optional[List[Tuple[str, int]]]:
return logs return logs
def clone_as_process(url: str, cwd: Optional[str], depth: int = -1) -> SimpleProcess: def clone(url: str, target_dir: Optional[str], depth: int = -1, custom_user: Optional[str] = None) -> SimpleProcess:
cmd = ['git', 'clone', url] cmd = ['git', 'clone', url]
if depth > 0: if depth > 0:
cmd.append('--depth={}'.format(depth)) cmd.append(f'--depth={depth}')
return SimpleProcess(cmd=cmd, cwd=cwd) if target_dir:
cmd.append(target_dir)
return SimpleProcess(cmd=cmd, custom_user=custom_user)

View File

@@ -1,20 +1,61 @@
import os import os
import re import re
from typing import Tuple, Optional, Set from typing import Optional, Set, Tuple
from bauh.commons.system import SimpleProcess, ProcessHandler, run_cmd from bauh.commons import system
from bauh.commons.system import ProcessHandler, SimpleProcess
from bauh.gems.arch import CUSTOM_MAKEPKG_FILE from bauh.gems.arch import CUSTOM_MAKEPKG_FILE
from bauh.gems.arch.proc_util import write_as_user
RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n')
RE_UNKNOWN_GPG_KEY = re.compile(r'\(unknown public key (\w+)\)') RE_UNKNOWN_GPG_KEY = re.compile(r'\(unknown public key (\w+)\)')
RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n')
def gen_srcinfo(build_dir: str, custom_pkgbuild_path: Optional[str] = None) -> str: def gen_srcinfo(build_dir: str, custom_pkgbuild_path: Optional[str] = None, custom_user: Optional[str] = None) -> str:
return run_cmd('makepkg --printsrcinfo{}'.format(' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''), cmd = f"makepkg --printsrcinfo{' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''}"
cwd=build_dir) return system.run_cmd(cmd, cwd=build_dir, custom_user=custom_user)
def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None) -> dict: def update_srcinfo(project_dir: str, custom_user: Optional[str] = None) -> bool:
updated_src = system.run_cmd('makepkg --printsrcinfo', cwd=project_dir, custom_user=custom_user)
if updated_src:
return write_as_user(content=updated_src, file_path=f"{project_dir}/.SRCINFO", user=custom_user)
return False
def list_output_files(project_dir: str, custom_pkgbuild_path: Optional[str] = None,
custom_user: Optional[str] = None) -> Set[str]:
cmd = f"makepkg --packagelist{' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''}"
output = system.run_cmd(cmd=cmd, print_error=False, cwd=project_dir, custom_user=custom_user)
if output:
return {p.strip() for p in output.split('\n') if p}
return set()
def build(pkgdir: str, optimize: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None,
custom_user: Optional[str] = None) -> Tuple[bool, str]:
cmd = ['makepkg', '-ALcsmf', '--skipchecksums', '--nodeps']
if custom_pkgbuild:
cmd.append('-p')
cmd.append(custom_pkgbuild)
if optimize:
if os.path.exists(CUSTOM_MAKEPKG_FILE):
handler.watcher.print(f'Using custom makepkg.conf -> {CUSTOM_MAKEPKG_FILE}')
cmd.append(f'--config={CUSTOM_MAKEPKG_FILE}')
else:
handler.watcher.print(f'Custom optimized makepkg.conf ({CUSTOM_MAKEPKG_FILE}) not found')
return handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True, custom_user=custom_user))
def check(project_dir: str, optimize: bool, missing_deps: bool, handler: ProcessHandler,
custom_pkgbuild: Optional[str] = None, custom_user: Optional[str] = None) -> dict:
res = {} res = {}
cmd = ['makepkg', '-ALcfm', '--check', '--noarchive', '--nobuild', '--noprepare'] cmd = ['makepkg', '-ALcfm', '--check', '--noarchive', '--nobuild', '--noprepare']
@@ -28,12 +69,12 @@ def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandl
if optimize: if optimize:
if os.path.exists(CUSTOM_MAKEPKG_FILE): if os.path.exists(CUSTOM_MAKEPKG_FILE):
handler.watcher.print('Using custom makepkg.conf -> {}'.format(CUSTOM_MAKEPKG_FILE)) handler.watcher.print(f'Using custom makepkg.conf -> {CUSTOM_MAKEPKG_FILE}')
cmd.append('--config={}'.format(CUSTOM_MAKEPKG_FILE)) cmd.append(f'--config={CUSTOM_MAKEPKG_FILE}')
else: else:
handler.watcher.print('Custom optimized makepkg.conf ( {} ) not found'.format(CUSTOM_MAKEPKG_FILE)) handler.watcher.print(f'Custom optimized makepkg.conf ({CUSTOM_MAKEPKG_FILE}) not found')
success, output = handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True)) success, output = handler.handle_simple(SimpleProcess(cmd, cwd=project_dir, shell=True, custom_user=custom_user))
if missing_deps and 'Missing dependencies' in output: if missing_deps and 'Missing dependencies' in output:
res['missing_deps'] = RE_DEPS_PATTERN.findall(output) res['missing_deps'] = RE_DEPS_PATTERN.findall(output)
@@ -47,42 +88,3 @@ def check(pkgdir: str, optimize: bool, missing_deps: bool, handler: ProcessHandl
res['validity_check'] = True res['validity_check'] = True
return res return res
def make(pkgdir: str, optimize: bool, handler: ProcessHandler, custom_pkgbuild: Optional[str] = None) -> Tuple[bool, str]:
cmd = ['makepkg', '-ALcsmf', '--skipchecksums', '--nodeps']
if custom_pkgbuild:
cmd.append('-p')
cmd.append(custom_pkgbuild)
if optimize:
if os.path.exists(CUSTOM_MAKEPKG_FILE):
handler.watcher.print('Using custom makepkg.conf -> {}'.format(CUSTOM_MAKEPKG_FILE))
cmd.append('--config={}'.format(CUSTOM_MAKEPKG_FILE))
else:
handler.watcher.print('Custom optimized makepkg.conf ( {} ) not found'.format(CUSTOM_MAKEPKG_FILE))
return handler.handle_simple(SimpleProcess(cmd, cwd=pkgdir, shell=True))
def update_srcinfo(project_dir: str) -> bool:
updated_src = run_cmd('makepkg --printsrcinfo', cwd=project_dir)
if updated_src:
with open('{}/.SRCINFO'.format(project_dir), 'w+') as f:
f.write(updated_src)
return True
return False
def list_output_files(project_dir: str, custom_pkgbuild_path: Optional[str] = None) -> Set[str]:
output = run_cmd(cmd='makepkg --packagelist{}'.format(' -p {}'.format(custom_pkgbuild_path) if custom_pkgbuild_path else ''),
print_error=False,
cwd=project_dir)
if output:
return {p.strip() for p in output.split('\n') if p}
return set()

View File

@@ -0,0 +1,51 @@
import multiprocessing
import os
import traceback
from pwd import getpwnam
from typing import Callable, Optional, TypeVar
R = TypeVar('R')
class CallAsUser:
def __init__(self, target: Callable[[], R], user: str):
self._target = target
self._user = user
def __call__(self, *args, **kwargs) -> R:
try:
os.setuid(getpwnam(self._user).pw_uid)
return self._target()
except:
traceback.print_exc()
class WriteToFile:
def __init__(self, file_path: str, content: str):
self._file_path = file_path
self._content = content
def __call__(self, *args, **kwargs) -> bool:
try:
with open(self._file_path, 'w+') as f:
f.write(self._content)
return True
except:
traceback.print_exc()
return False
def exec_as_user(target: Callable[[], R], user: Optional[str] = None) -> R:
if user:
with multiprocessing.Pool(1) as pool:
return pool.apply(CallAsUser(target, user))
else:
return target()
def write_as_user(content: str, file_path: str, user: Optional[str] = None) -> bool:
return exec_as_user(target=WriteToFile(file_path=file_path, content=content),
user=user)

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Sestan llegint les revisions del dipòsit
arch.downgrade.repo_pkg.no_versions=No s'ha trobat cap versió antiga al disc arch.downgrade.repo_pkg.no_versions=No s'ha trobat cap versió antiga al disc
arch.downgrade.searching_stored=Cerqueu versions antigues al disc arch.downgrade.searching_stored=Cerqueu versions antigues al disc
arch.downgrade.version_found=Sha trobat la versió actual del paquet arch.downgrade.version_found=Sha trobat la versió actual del paquet
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=PKGBUILD arch.info.00_pkg_build=PKGBUILD
arch.info.01_id=identificació arch.info.01_id=identificació
arch.info.02_name=nom arch.info.02_name=nom

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Repository Commits lesen
arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.repo_pkg.no_versions=No old version found on disk
arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.searching_stored=Looking for old versions on disk
arch.downgrade.version_found=Aktuelle Paketversion gefunden arch.downgrade.version_found=Aktuelle Paketversion gefunden
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=Ich würde arch.info.01_id=Ich würde
arch.info.02_name=Name arch.info.02_name=Name

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Reading the repository commits
arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.repo_pkg.no_versions=No old version found on disk
arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.searching_stored=Looking for old versions on disk
arch.downgrade.version_found=Current package version found arch.downgrade.version_found=Current package version found
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=id arch.info.01_id=id
arch.info.02_name=name arch.info.02_name=name

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Leyendo los commits del repositorio
arch.downgrade.repo_pkg.no_versions=No se encontró una versión anterior en el disco arch.downgrade.repo_pkg.no_versions=No se encontró una versión anterior en el disco
arch.downgrade.searching_stored=Buscando versiones antiguas en el disco arch.downgrade.searching_stored=Buscando versiones antiguas en el disco
arch.downgrade.version_found=Version actual del paquete encontrada arch.downgrade.version_found=Version actual del paquete encontrada
arch.aur.error.missing_root_dep={dep} no está instalado y es necesario para la instalación de paquetes del {aur} como el usuario {root}
arch.aur.error.add_builder_user=No fue posible crear el usuario {user} para construir paquetes del {aur}
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=id arch.info.01_id=id
arch.info.02_name=nombre arch.info.02_name=nombre

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Lecture des commits du dépôt
arch.downgrade.repo_pkg.no_versions=Aucune version antérieure trouvée sur le disque arch.downgrade.repo_pkg.no_versions=Aucune version antérieure trouvée sur le disque
arch.downgrade.searching_stored=Recherche de version antérieure sur le disque arch.downgrade.searching_stored=Recherche de version antérieure sur le disque
arch.downgrade.version_found=Version actuelle du paquet trouvée arch.downgrade.version_found=Version actuelle du paquet trouvée
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=id arch.info.01_id=id
arch.info.02_name=nom arch.info.02_name=nom

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Reading the repository commits
arch.downgrade.repo_pkg.no_versions=Nessuna versione precedente trovata sul disco arch.downgrade.repo_pkg.no_versions=Nessuna versione precedente trovata sul disco
arch.downgrade.searching_stored=Ricerca di versioni precedenti su disco arch.downgrade.searching_stored=Ricerca di versioni precedenti su disco
arch.downgrade.version_found=Trovata la versione del pacchetto corrente arch.downgrade.version_found=Trovata la versione del pacchetto corrente
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=id arch.info.01_id=id
arch.info.02_name=nome arch.info.02_name=nome

View File

@@ -125,6 +125,8 @@ arch.downgrade.reading_commits=Lendo os commits do repositório
arch.downgrade.repo_pkg.no_versions=Nenhuma versão antiga encontrada no disco arch.downgrade.repo_pkg.no_versions=Nenhuma versão antiga encontrada no disco
arch.downgrade.searching_stored=Procurando versões antigas no disco arch.downgrade.searching_stored=Procurando versões antigas no disco
arch.downgrade.version_found=Versão atual do pacote encontrada arch.downgrade.version_found=Versão atual do pacote encontrada
arch.aur.error.missing_root_dep={dep} não está instalado e é necessário para a instalação de pacotes do {aur} como o usuário {root}
arch.aur.error.add_builder_user=Não foi possível criar o usuário {user} para a construção de pacotes do {aur}
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=id arch.info.01_id=id
arch.info.02_name=nome arch.info.02_name=nome

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Чтение коммитов репозитор
arch.downgrade.repo_pkg.no_versions=No old version found on disk arch.downgrade.repo_pkg.no_versions=No old version found on disk
arch.downgrade.searching_stored=Looking for old versions on disk arch.downgrade.searching_stored=Looking for old versions on disk
arch.downgrade.version_found=Найдена текущая версия пакета arch.downgrade.version_found=Найдена текущая версия пакета
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=PKGBUILD arch.info.00_pkg_build=PKGBUILD
arch.info.01_id=Идентификатор arch.info.01_id=Идентификатор
arch.info.02_name=Имя arch.info.02_name=Имя

View File

@@ -126,6 +126,8 @@ arch.downgrade.reading_commits=Depo çalışmalarını oku
arch.downgrade.repo_pkg.no_versions=Diskte eski sürüm bulunamadı arch.downgrade.repo_pkg.no_versions=Diskte eski sürüm bulunamadı
arch.downgrade.searching_stored=Diskteki eski sürümlere bakılıyor arch.downgrade.searching_stored=Diskteki eski sürümlere bakılıyor
arch.downgrade.version_found=Geçerli paket sürümü bulundu arch.downgrade.version_found=Geçerli paket sürümü bulundu
arch.aur.error.missing_root_dep={dep} is not installed and is required for installing {aur} packages as the {root} user
arch.aur.error.add_builder_user=It was not possible to create the user {user} for building {aur} packages
arch.info.00_pkg_build=pkgbuild arch.info.00_pkg_build=pkgbuild
arch.info.01_id=kimlik arch.info.01_id=kimlik
arch.info.02_name=isim arch.info.02_name=isim

8
bauh/gems/arch/sshell.py Normal file
View File

@@ -0,0 +1,8 @@
from typing import Optional, Tuple
from bauh.commons.system import execute
def mkdir(dir_path: str, parent: bool = True, custom_user: Optional[str] = None) -> Tuple[bool, Optional[str]]:
code, output = execute(f'mkdir {"-p " if parent else ""}"{dir_path}"', shell=True, custom_user=custom_user)
return code == 0, output

View File

@@ -124,7 +124,7 @@ class SnapManager(SoftwareManager):
return ProcessHandler(watcher).handle_simple(snap.downgrade_and_stream(pkg.name, root_password))[0] return ProcessHandler(watcher).handle_simple(snap.downgrade_and_stream(pkg.name, root_password))[0]
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> SystemProcess: def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> SystemProcess:
raise Exception("'upgrade' is not supported by {}".format(SnapManager.__class__.__name__)) raise Exception(f"'upgrade' is not supported by {SnapManager.__class__.__name__}")
def uninstall(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: def uninstall(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult:
if snap.is_installed() and snapd.is_running(): if snap.is_installed() and snapd.is_running():
@@ -173,7 +173,7 @@ class SnapManager(SoftwareManager):
return info return info
def get_history(self, pkg: SnapApplication) -> PackageHistory: def get_history(self, pkg: SnapApplication) -> PackageHistory:
raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__)) raise Exception(f"'get_history' is not supported by {pkg.__class__.__name__}")
def install(self, pkg: SnapApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: def install(self, pkg: SnapApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult:
# retrieving all installed so it will be possible to know the additional installed runtimes after the operation succeeds # retrieving all installed so it will be possible to know the additional installed runtimes after the operation succeeds
@@ -210,20 +210,20 @@ class SnapManager(SoftwareManager):
if channels: if channels:
opts = [InputOption(label=c[0], value=c[1]) for c in channels] opts = [InputOption(label=c[0], value=c[1]) for c in channels]
channel_select = SingleSelectComponent(type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0]) channel_select = SingleSelectComponent(type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0])
body = '<p>{}.</p>'.format(self.i18n['snap.install.available_channels.message'].format(bold(self.i18n['stable']), bold(pkg.name))) body = f"<p>{self.i18n['snap.install.available_channels.message'].format(bold(self.i18n['stable']), bold(pkg.name))}.</p>"
body += '<p>{}:</p>'.format(self.i18n['snap.install.available_channels.help']) body += f"<p>{self.i18n['snap.install.available_channels.help']}:</p>"
if watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'], if watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'],
body=body, body=body,
components=[channel_select], components=[channel_select],
confirmation_label=self.i18n['continue'], confirmation_label=self.i18n['continue'],
deny_label=self.i18n['cancel']): deny_label=self.i18n['cancel']):
self.logger.info("Installing '{}' with the custom command '{}'".format(pkg.name, channel_select.value)) self.logger.info(f"Installing '{pkg.name}' with the custom command '{channel_select.value}'")
res = ProcessHandler(watcher).handle(SystemProcess(new_root_subprocess(channel_select.value.value.split(' '), root_password=root_password))) res = ProcessHandler(watcher).handle(SystemProcess(new_root_subprocess(channel_select.value.value.split(' '), root_password=root_password)))
return self._gen_installation_response(success=res, pkg=pkg, return self._gen_installation_response(success=res, pkg=pkg,
installed=installed_names, disk_loader=disk_loader) installed=installed_names, disk_loader=disk_loader)
else: else:
self.logger.error("Could not find available channels in the installation output: {}".format(output)) self.logger.error(f"Could not find available channels in the installation output: {output}")
return self._gen_installation_response(success=res, pkg=pkg, installed=installed_names, disk_loader=disk_loader) return self._gen_installation_response(success=res, pkg=pkg, installed=installed_names, disk_loader=disk_loader)
@@ -321,14 +321,13 @@ class SnapManager(SoftwareManager):
if not snapd.is_running(): if not snapd.is_running():
snap_bold = bold('Snap') snap_bold = bold('Snap')
return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold), return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold),
self.i18n['snap.notification.snap.disable'].format(snap_bold, bold( self.i18n['snap.notification.snap.disable'].format(snap_bold,
'{} > {}'.format(self.i18n['settings'].capitalize(), bold(f"{self.i18n['settings'].capitalize()} > {self.i18n['core.config.tab.types']}"))]
self.i18n['core.config.tab.types'])))]
elif internet_available: elif internet_available:
available, output = snap.is_api_available() available, output = snap.is_api_available()
if not available: if not available:
self.logger.warning('It seems Snap API is not available. Search output: {}'.format(output)) self.logger.warning(f'It seems Snap API is not available. Search output: {output}')
return [self.i18n['snap.notifications.api.unavailable'].format(bold('Snaps'), bold('Snap'))] return [self.i18n['snap.notifications.api.unavailable'].format(bold('Snaps'), bold('Snap'))]
def _fill_suggestion(self, name: str, priority: SuggestionPriority, snapd_client: SnapdClient, out: List[PackageSuggestion]): def _fill_suggestion(self, name: str, priority: SuggestionPriority, snapd_client: SnapdClient, out: List[PackageSuggestion]):
@@ -347,7 +346,7 @@ class SnapManager(SoftwareManager):
out.append(sug) out.append(sug)
return return
self.logger.warning("Could not retrieve suggestion '{}'".format(name)) self.logger.warning(f"Could not retrieve suggestion '{name}'")
def _map_to_app(self, app_json: dict, installed: bool, disk_loader: Optional[DiskCacheLoader] = None, is_application: bool = False) -> SnapApplication: def _map_to_app(self, app_json: dict, installed: bool, disk_loader: Optional[DiskCacheLoader] = None, is_application: bool = False) -> SnapApplication:
app = SnapApplication(id=app_json.get('id'), app = SnapApplication(id=app_json.get('id'),
@@ -382,11 +381,11 @@ class SnapManager(SoftwareManager):
res = [] res = []
if snapd.is_running(): if snapd.is_running():
self.logger.info('Downloading suggestions file {}'.format(SUGGESTIONS_FILE)) self.logger.info(f'Downloading suggestions file {SUGGESTIONS_FILE}')
file = self.http_client.get(SUGGESTIONS_FILE) file = self.http_client.get(SUGGESTIONS_FILE)
if not file or not file.text: if not file or not file.text:
self.logger.warning("No suggestion found in {}".format(SUGGESTIONS_FILE)) self.logger.warning(f"No suggestion found in {SUGGESTIONS_FILE}")
return res return res
else: else:
self.logger.info('Mapping suggestions') self.logger.info('Mapping suggestions')
@@ -437,7 +436,7 @@ class SnapManager(SoftwareManager):
else: else:
cmd = commands[0]['name'] cmd = commands[0]['name']
self.logger.info("Running '{}': {}".format(pkg.name, cmd)) self.logger.info(f"Running '{pkg.name}': {cmd}")
snap.run(cmd) snap.run(cmd)
def get_screenshots(self, pkg: SnapApplication) -> List[str]: def get_screenshots(self, pkg: SnapApplication) -> List[str]:
@@ -482,19 +481,19 @@ class SnapManager(SoftwareManager):
try: try:
data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name] data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name]
except: except:
self.logger.warning("snapd client could not retrieve channels for '{}'".format(pkg.name)) self.logger.warning(f"snapd client could not retrieve channels for '{pkg.name}'")
return return
if not data: if not data:
self.logger.warning("snapd client could find a match for name '{}' when retrieving its channels".format(pkg.name)) self.logger.warning(f"snapd client could find a match for name '{pkg.name}' when retrieving its channels")
else: else:
if not data[0].get('channels'): if not data[0].get('channels'):
self.logger.info("No channel available for '{}'. Skipping selection.".format(pkg.name)) self.logger.info(f"No channel available for '{pkg.name}'. Skipping selection.")
else: else:
if pkg.channel: if pkg.channel:
current_channel = pkg.channel if '/' in pkg.channel else 'latest/{}'.format(pkg.channel) current_channel = pkg.channel if '/' in pkg.channel else f'latest/{pkg.channel}'
else: else:
current_channel = 'latest/{}'.format(data[0].get('channel', 'stable')) current_channel = f"latest/{data[0].get('channel', 'stable')}"
opts = [] opts = []
def_opt = None def_opt = None
@@ -510,7 +509,7 @@ class SnapManager(SoftwareManager):
def_opt = op def_opt = op
if not opts: if not opts:
self.logger.info("No different channel available for '{}'. Skipping selection.".format(pkg.name)) self.logger.info(f"No different channel available for '{pkg.name}'. Skipping selection.")
return return
select = SingleSelectComponent(label='', select = SingleSelectComponent(label='',

View File

@@ -70,7 +70,7 @@ class SnapApplication(SoftwarePackage):
return self.type == 'app' return self.type == 'app'
def get_disk_cache_path(self): def get_disk_cache_path(self):
return super(SnapApplication, self).get_disk_cache_path() + '/installed/' + self.name return f'{super(SnapApplication, self).get_disk_cache_path()}/installed/{self.name}'
def is_trustable(self) -> bool: def is_trustable(self) -> bool:
return self.verified_publisher return self.verified_publisher

View File

@@ -27,7 +27,7 @@ def install_and_stream(app_name: str, confinement: str, root_password: str, chan
install_cmd.append('--classic') install_cmd.append('--classic')
if channel: if channel:
install_cmd.append('--channel={}'.format(channel)) install_cmd.append(f'--channel={channel}')
return SimpleProcess(install_cmd, root_password=root_password, shell=True) return SimpleProcess(install_cmd, root_password=root_password, shell=True)
@@ -42,7 +42,7 @@ def refresh_and_stream(app_name: str, root_password: str, channel: Optional[str]
cmd = [BASE_CMD, 'refresh', app_name] cmd = [BASE_CMD, 'refresh', app_name]
if channel: if channel:
cmd.append('--channel={}'.format(channel)) cmd.append(f'--channel={channel}')
return SimpleProcess(cmd=cmd, return SimpleProcess(cmd=cmd,
root_password=root_password, root_password=root_password,
@@ -51,7 +51,7 @@ def refresh_and_stream(app_name: str, root_password: str, channel: Optional[str]
def run(cmd: str): def run(cmd: str):
subprocess.Popen(['snap run {}'.format(cmd)], shell=True, env={**os.environ}) subprocess.Popen([f'{BASE_CMD} run {cmd}'], shell=True, env={**os.environ})
def is_api_available() -> Tuple[bool, str]: def is_api_available() -> Tuple[bool, str]:

View File

@@ -55,7 +55,7 @@ class SnapdClient:
final_query = query.strip() final_query = query.strip()
if final_query and self.session: if final_query and self.session:
res = self.session.get(url='{}/find'.format(URL_BASE), params={'q': final_query}) res = self.session.get(url=f'{URL_BASE}/find', params={'q': final_query})
if res.status_code == 200: if res.status_code == 200:
json_res = res.json() json_res = res.json()
@@ -65,7 +65,7 @@ class SnapdClient:
def find_by_name(self, name: str) -> Optional[List[dict]]: def find_by_name(self, name: str) -> Optional[List[dict]]:
if name and self.session: if name and self.session:
res = self.session.get('{}/find?name={}'.format(URL_BASE, name)) res = self.session.get(f'{URL_BASE}/find?name={name}')
if res.status_code == 200: if res.status_code == 200:
json_res = res.json() json_res = res.json()
@@ -75,7 +75,7 @@ class SnapdClient:
def list_all_snaps(self) -> List[dict]: def list_all_snaps(self) -> List[dict]:
if self.session: if self.session:
res = self.session.get('{}/snaps'.format(URL_BASE)) res = self.session.get(f'{URL_BASE}/snaps')
if res.status_code == 200: if res.status_code == 200:
json_res = res.json() json_res = res.json()
@@ -87,7 +87,7 @@ class SnapdClient:
def list_only_apps(self) -> List[dict]: def list_only_apps(self) -> List[dict]:
if self.session: if self.session:
res = self.session.get('{}/apps'.format(URL_BASE)) res = self.session.get(f'{URL_BASE}/apps')
if res.status_code == 200: if res.status_code == 200:
json_res = res.json() json_res = res.json()
@@ -98,7 +98,7 @@ class SnapdClient:
def list_commands(self, name: str) -> List[dict]: def list_commands(self, name: str) -> List[dict]:
if self.session: if self.session:
res = self.session.get('{}/apps?names={}'.format(URL_BASE, name)) res = self.session.get(f'{URL_BASE}/apps?names={name}')
if res.status_code == 200: if res.status_code == 200:
json_res = res.json() json_res = res.json()

View File

@@ -45,7 +45,8 @@ def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logg
internet_checker=InternetChecker(offline=app_args.offline), internet_checker=InternetChecker(offline=app_args.offline),
root_user=user.is_root()) root_user=user.is_root())
managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config, default_locale=DEFAULT_I18N_KEY) managers = gems.load_managers(context=context, locale=i18n.current_key, config=app_config,
default_locale=DEFAULT_I18N_KEY, logger=logger)
if app_args.reset: if app_args.reset:
util.clean_app_files(managers) util.clean_app_files(managers)

View File

@@ -1,12 +1,15 @@
import inspect import inspect
import os import os
import pkgutil import pkgutil
from typing import List from logging import Logger
from typing import List, Generator
from bauh import ROOT_DIR from bauh import __app_name__, ROOT_DIR
from bauh.api.abstract.controller import SoftwareManager, ApplicationContext from bauh.api.abstract.controller import SoftwareManager, ApplicationContext
from bauh.view.util import translation from bauh.view.util import translation
FORBIDDEN_GEMS_FILE = f'/etc/{__app_name__}/gems.forbidden'
def find_manager(member): def find_manager(member):
if not isinstance(member, str): if not isinstance(member, str):
@@ -19,12 +22,34 @@ def find_manager(member):
return manager_found return manager_found
def load_managers(locale: str, context: ApplicationContext, config: dict, default_locale: str) -> List[SoftwareManager]: def read_forbidden_gems() -> Generator[str, None, None]:
try:
with open(FORBIDDEN_GEMS_FILE) as f:
forbidden_lines = f.readlines()
for line in forbidden_lines:
clean_line = line.strip()
if clean_line and not clean_line.startswith('#'):
yield clean_line
except FileNotFoundError:
pass
def load_managers(locale: str, context: ApplicationContext, config: dict, default_locale: str, logger: Logger) -> List[SoftwareManager]:
managers = [] managers = []
for f in os.scandir(ROOT_DIR + '/gems'): forbidden_gems = {gem for gem in read_forbidden_gems()}
for f in os.scandir(f'{ROOT_DIR}/gems'):
if f.is_dir() and f.name != '__pycache__': if f.is_dir() and f.name != '__pycache__':
loader = pkgutil.find_loader('bauh.gems.{}.controller'.format(f.name))
if f.name in forbidden_gems:
logger.warning(f"gem '{f.name}' could not be loaded because it was marked as forbidden in '{FORBIDDEN_GEMS_FILE}'")
continue
loader = pkgutil.find_loader(f'bauh.gems.{f.name}.controller')
if loader: if loader:
module = loader.load_module() module = loader.load_module()
@@ -33,7 +58,7 @@ def load_managers(locale: str, context: ApplicationContext, config: dict, defaul
if manager_class: if manager_class:
if locale: if locale:
locale_path = '{}/resources/locale'.format(f.path) locale_path = f'{f.path}/resources/locale'
if os.path.exists(locale_path): if os.path.exists(locale_path):
context.i18n.current.update(translation.get_locale_keys(locale, locale_path)[1]) context.i18n.current.update(translation.get_locale_keys(locale, locale_path)[1])
@@ -51,4 +76,3 @@ def load_managers(locale: str, context: ApplicationContext, config: dict, defaul
managers.append(man) managers.append(man)
return managers return managers