diff --git a/docs/usage.rst b/docs/usage.rst index e0286b5..ab05952 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -874,6 +874,29 @@ strip_release provided Instead of the package version, return the version this package provides. Its value is what the package provides, and ``strip_release`` takes effect too. This is best used with libraries. +Check ALPM files database +~~~~~~~~~~~~~~~~~~~~~~~~~ +:: + + source = "alpmfiles" + +Search package files in a local ALPM files database. The package does not need to be installed. This can be useful for checking shared library versions if a package does not list them in its ``provides``. + +pkgname + Name of the package. + +filename + Regular expression for the file path. If it contains one matching group, that group is returned. Otherwise return the whole file path. Paths do not have an initial slash. For example, ``usr/lib/libuv\\.so\\.([^.]+)`` matches the major shared library version of libuv. + +repo + Name of the package repository in which the package resides. If not provided, search all repositories. + +strip_dir + Strip directory from the path before matching. Defaults to ``false``. + +dbpath + Path to the ALPM database directory. Default: ``/var/lib/pacman``. You need to update the database yourself with ``pacman -Fy``. + Check Open Vsx ~~~~~~~~~~~~~~~ :: diff --git a/nvchecker_source/alpmfiles.py b/nvchecker_source/alpmfiles.py new file mode 100644 index 0000000..c8ff85a --- /dev/null +++ b/nvchecker_source/alpmfiles.py @@ -0,0 +1,50 @@ +# MIT licensed +# Copyright (c) 2023 Pekka Ristola , et al. + +from asyncio import create_subprocess_exec +from asyncio.subprocess import PIPE +import re + +from nvchecker.api import GetVersionError + +async def get_files(info: tuple) -> list: + dbpath, pkg = info + # there's no pyalpm bindings for the file databases + cmd = ['pacman', '-Flq', '--dbpath', dbpath, pkg] + + p = await create_subprocess_exec(*cmd, stdout = PIPE, stderr = PIPE) + stdout, stderr = await p.communicate() + + if p.returncode == 0: + return stdout.decode().splitlines() + else: + raise GetVersionError( + 'pacman failed to get file list', + pkg = pkg, + cmd = cmd, + stdout = stdout.decode(errors='replace'), + stderr = stderr.decode(errors='replace'), + returncode = p.returncode, + ) + +async def get_version(name, conf, *, cache, **kwargs): + pkg = conf['pkgname'] + repo = conf.get('repo') + if repo is not None: + pkg = f'{repo}/{pkg}' + dbpath = conf.get('dbpath', '/var/lib/pacman') + regex = re.compile(conf['filename']) + if regex.groups > 1: + raise GetVersionError('multi-group regex') + strip_dir = conf.get('strip_dir', False) + + files = await cache.get((dbpath, pkg), get_files) + + for f in files: + fn = f.rsplit('/', 1)[-1] if strip_dir else f + match = regex.fullmatch(fn) + if match: + groups = match.groups() + return groups[0] if len(groups) > 0 else fn + + raise GetVersionError('no file matches specified regex') diff --git a/tests/test_alpmfiles.py b/tests/test_alpmfiles.py new file mode 100644 index 0000000..858ece1 --- /dev/null +++ b/tests/test_alpmfiles.py @@ -0,0 +1,28 @@ +# MIT licensed +# Copyright (c) 2023 Pekka Ristola , et al. + +import shutil + +import pytest + +pytestmark = [ + pytest.mark.asyncio, + pytest.mark.skipif(shutil.which('pacman') is None, reason='requires pacman command'), +] + +async def test_alpmfiles(get_version): + assert await get_version('test', { + 'source': 'alpmfiles', + 'pkgname': 'libuv', + 'filename': 'usr/lib/libuv\\.so\\.([^.]+)', + }) == '1' + +async def test_alpmfiles_strip(get_version): + assert await get_version('test', { + 'source': 'alpmfiles', + 'pkgname': 'glibc', + 'repo': 'core', + 'filename': 'libc\\.so\\.[^.]+', + 'strip_dir': True, + 'dbpath': '/var/lib/pacman', + }) == 'libc.so.6'