fix(void): carry python3-pysocks and constrain the audio and SOCKS floors
CI / security (pull_request) Successful in 1m13s
CI / lint (pull_request) Successful in 23s
CI / ai-review (pull_request) Successful in 8s

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:
2026-11-06 21:32:33 +05:30
parent b47a65c91e
commit 06b7b79664
10 changed files with 240 additions and 38 deletions
+68 -5
View File
@@ -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()