fix(void): carry python3-pysocks and constrain the audio and SOCKS floors
Void's stock python3-PyAudio (0.2.12_5) and python3-pysocks (1.7.0_8) sit below the project floors (pyaudio>=0.2.13, PySocks>=1.7.1). voculinux depended on both by bare name, which an installed stock package already satisfies, so a user who had the stock packages was never upgraded. - add packaging/void/srcpkgs/python3-pysocks: PySocks 1.7.1 from the PyPI sdist that uv.lock pins (sha256 3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0) - voculinux depends on python3-PyAudio>=0.2.14_1 and python3-pysocks>=1.7.1_1 - the install smoke installs the stock packages first, then voculinux, and fails unless each reads back as its carried pkgver - tests: each constraint must name its carried template's current version and revision; the stock version must sit below the floor; the depends parser refuses malformed tokens instead of stripping them - docs: "Version floors" in packaging/void/README.md; a pointer in docs/RELEASE_PROCESS.md Refs #16
This commit is contained in:
@@ -23,6 +23,8 @@ import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
VOID = REPO_ROOT / "packaging" / "void"
|
||||
SRC = VOID / "srcpkgs"
|
||||
@@ -128,6 +130,19 @@ VERIFIED_VOID_PACKAGES = frozenset(
|
||||
#: never by hiding it behind one of these.
|
||||
FORBIDDEN_VARS = ("noverifypydeps", "nopyprovides")
|
||||
|
||||
#: Carried templates whose stock Void package sits below the project floor
|
||||
#: (tests/test_void_small_deps.py, GAP_PACKAGES). voculinux must require each
|
||||
#: one as name>=<carried version>_<revision>. A bare name is satisfied by the
|
||||
#: stock package a user already has, so it would never be upgraded.
|
||||
GAP_DEPENDS = ("python3-PyAudio", "python3-pysocks")
|
||||
|
||||
#: One xbps dependency token: a bare name, or name, comparator and
|
||||
#: version_revision. The whole token must match; a malformed token such as
|
||||
#: "python3-PyAudio>=" is refused, never stripped down to its name.
|
||||
DEPENDS_TOKEN = re.compile(
|
||||
r"(?P<name>[A-Za-z0-9+._-]+)(?:(?P<op>>=|<=|>|<)(?P<version>[0-9][A-Za-z0-9.]*_[0-9]+))?"
|
||||
)
|
||||
|
||||
|
||||
def _template_text() -> str:
|
||||
"""Read the raw template file."""
|
||||
@@ -193,6 +208,31 @@ def _project_dependencies() -> list:
|
||||
return list(data["project"]["dependencies"])
|
||||
|
||||
|
||||
def _depends_tokens(value: str) -> list:
|
||||
"""Validated tokens of an xbps depends value; a malformed token fails the test."""
|
||||
tokens = []
|
||||
for token in value.split():
|
||||
if token.startswith("${"):
|
||||
continue
|
||||
assert DEPENDS_TOKEN.fullmatch(token), f"malformed dependency token: {token!r}"
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def _depends_names(value: str) -> set:
|
||||
"""Package names of an xbps depends value, with any version constraint removed."""
|
||||
return {DEPENDS_TOKEN.fullmatch(token).group("name") for token in _depends_tokens(value)}
|
||||
|
||||
|
||||
def _carried_pkgver(name: str) -> str:
|
||||
"""The pkgver a carried template builds: its version and revision, as written."""
|
||||
text = (SRC / name / "template").read_text(encoding="utf-8")
|
||||
version = re.search(r"^version=(\S+)$", text, re.M)
|
||||
revision = re.search(r"^revision=(\d+)$", text, re.M)
|
||||
assert version and revision, f"{name} template lacks a version or revision"
|
||||
return f"{version.group(1)}_{revision.group(1)}"
|
||||
|
||||
|
||||
def test_app_template_lives_in_void_packages_layout() -> None:
|
||||
"""The app template sits where xbps-src expects it: srcpkgs/<pkgname>."""
|
||||
assert TEMPLATE.is_file(), f"{TEMPLATE} is missing"
|
||||
@@ -275,7 +315,7 @@ def test_checksum_is_a_recorded_digest_or_the_build_placeholder() -> None:
|
||||
|
||||
def test_every_project_dependency_is_covered() -> None:
|
||||
"""pyproject.toml runtime deps plus substrate must all resolve to depends."""
|
||||
depends = set(_template_vars()["depends"].split())
|
||||
depends = _depends_names(_template_vars()["depends"])
|
||||
uncovered = []
|
||||
for clause in _project_dependencies():
|
||||
pip_name = re.match(r"^([A-Za-z0-9._-]+)", clause).group(1).lower().replace("_", "-")
|
||||
@@ -293,14 +333,37 @@ def test_template_dependencies_exist_in_void_packages() -> None:
|
||||
variables = _template_vars()
|
||||
referenced = set()
|
||||
for key in ("depends", "hostmakedepends", "makedepends", "checkdepends"):
|
||||
for token in variables.get(key, "").split():
|
||||
if token.startswith("${"):
|
||||
continue
|
||||
referenced.add(token)
|
||||
referenced |= _depends_names(variables.get(key, ""))
|
||||
unknown = sorted(referenced - VERIFIED_VOID_PACKAGES)
|
||||
assert not unknown, f"template references unverified packages: {unknown}"
|
||||
|
||||
|
||||
def test_depends_tokens_are_validated_not_stripped() -> None:
|
||||
"""A constrained token is accepted only in full; a truncated one is malformed."""
|
||||
assert DEPENDS_TOKEN.fullmatch("python3-PyAudio>=0.2.14_1")
|
||||
assert DEPENDS_TOKEN.fullmatch("python3-pysocks")
|
||||
for malformed in ("python3-PyAudio>=", "python3-PyAudio>=0.2.14", ">=0.2.14_1"):
|
||||
assert not DEPENDS_TOKEN.fullmatch(malformed), malformed
|
||||
with pytest.raises(AssertionError):
|
||||
_depends_tokens(malformed)
|
||||
|
||||
|
||||
def test_gap_packages_are_version_constrained_to_their_carried_templates() -> None:
|
||||
"""voculinux requires name>=<carried pkgver>, never the bare name.
|
||||
|
||||
A bare name is satisfied by the stock package a user already has (below the
|
||||
floor), so it would keep the stock package. The constraint must track the
|
||||
carried template: bumping its version or revision without this line fails.
|
||||
"""
|
||||
tokens = _depends_tokens(_template_vars()["depends"])
|
||||
for name in GAP_DEPENDS:
|
||||
matching = [
|
||||
token for token in tokens if DEPENDS_TOKEN.fullmatch(token).group("name") == name
|
||||
]
|
||||
expected = f"{name}>={_carried_pkgver(name)}"
|
||||
assert matching == [expected], f"depends must name {expected}; found {matching}"
|
||||
|
||||
|
||||
def test_no_stale_indicator_library_name() -> None:
|
||||
"""The pre-rename libappindicator names do not exist in void-packages."""
|
||||
variables = _template_vars()
|
||||
|
||||
Reference in New Issue
Block a user