feat(packaging): AUR -bin package publishing with per-arch digests (#879)
* feat(aur): add vocalinux-bin PKGBUILD shipping the AppImage Closes #817 * fix(aur): hard-dep injection tools, pin real digests, gate the -bin package * fix(aur): pin both-arch -bin digests and build the binpkg as builder updpkgsums in the publish action only regenerates the x86_64 sum array, leaving aarch64 stale after every tag; release.yml now pins both AppImage digests itself and publishes with updpkgsums off. The AUR gate also created $WORKDIR/binpkg as root after handing WORKDIR to builder, so makepkg could not write there. AUR.md now matches the published -bin package. * fix(ci): wait for aarch64 AppImage before publishing vocalinux-bin publish-aur only needed build-and-release, so on a tag the digest-pinning step could curl the aarch64 AppImage before build-appimage-arm64 attached it — a 404 that failed the job between the source and -bin publishes. Split the -bin bump/pin/publish steps into publish-aur-bin, which needs [publish-aur, build-appimage-arm64]: the source package still ships as early as before while the binary package only runs once both AppImages exist on the release. * fix(ci): ship -bin local sources and wait for the aarch64 asset on AUR publish Split the -bin bump/pin/publish into publish-aur-bin needing [publish-aur, build-appimage-arm64] so the digest-pinning step can no longer curl a 404 for the not-yet-attached aarch64 AppImage mid-release, and publish with asset_dir so the PKGBUILD's local sources (vocalinux.desktop, vocalinux.svg, LICENSE) reach the AUR repo instead of a lone PKGBUILD users cannot build. * style(tests): apply black formatting to aur packaging test --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
co-authored by
Jatin K Malik
parent
5c031e64fb
commit
b4babcaeca
@@ -12,6 +12,8 @@ from pathlib import Path
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
AUR = REPO_ROOT / "packaging" / "aur"
|
||||
PKGBUILD = AUR / "vocalinux" / "PKGBUILD"
|
||||
BIN_DIR = AUR / "vocalinux-bin"
|
||||
BIN_PKGBUILD = BIN_DIR / "PKGBUILD"
|
||||
GATE_SH = AUR / "build-test.sh"
|
||||
WORKFLOWS = REPO_ROOT / ".github" / "workflows"
|
||||
PIPELINE = WORKFLOWS / "unified-pipeline.yml"
|
||||
@@ -319,3 +321,128 @@ def test_every_selectable_engine_reaches_optdepends() -> None:
|
||||
"these optional engines have no optdepends line, so an AUR user who"
|
||||
f" selects them gets an ImportError: {', '.join(missing)}"
|
||||
)
|
||||
|
||||
|
||||
def _bin_pkgbuild() -> str:
|
||||
return BIN_PKGBUILD.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_bin_package_ships_the_release_appimage_per_arch() -> None:
|
||||
"""#817: the -bin package is the prebuilt AppImage, fetched per release
|
||||
architecture — x86_64 and aarch64, never 'any', because a binary is not
|
||||
arch-independent the way the build-from-source package is."""
|
||||
text = _bin_pkgbuild()
|
||||
assert "arch=('x86_64' 'aarch64')" in text
|
||||
for arch in ("x86_64", "aarch64"):
|
||||
asset = f"Vocalinux-${{_tag}}-{arch}.AppImage"
|
||||
assert (
|
||||
f'"{asset}::https://github.com/VocaHQ/vocalinux/releases/download/'
|
||||
f"v${{_tag}}/{asset}" in text
|
||||
), f"source_{arch} does not fetch {asset} from the v${{_tag}} release"
|
||||
|
||||
|
||||
def test_bin_package_installs_the_appimage_intact_and_runnable() -> None:
|
||||
"""options=('!strip') keeps pacman from rewriting the self-mounting ELF;
|
||||
fuse2 is the one real dependency — the type-2 runtime mounts its squashfs
|
||||
through FUSE. The binary lands under /opt and is symlinked into PATH."""
|
||||
text = _bin_pkgbuild()
|
||||
assert "options=('!strip')" in text
|
||||
assert "'fuse2'" in text
|
||||
assert 'install -Dm755 "Vocalinux-${_tag}-${CARCH}.AppImage"' in text
|
||||
assert '"/opt/${pkgname}/' in text
|
||||
assert "ln -s" in text and '"${pkgdir}/usr/bin/vocalinux"' in text
|
||||
|
||||
|
||||
def test_bin_package_conflicts_with_the_source_and_git_packages() -> None:
|
||||
"""All three packages ship /usr/bin/vocalinux; provides= lets a depends on
|
||||
'vocalinux' resolve to the -bin install."""
|
||||
text = _bin_pkgbuild()
|
||||
assert "provides=('vocalinux')" in text
|
||||
conflicts = re.search(r"conflicts=\(([^)]*)\)", text)
|
||||
assert conflicts, "vocalinux-bin has no conflicts=()"
|
||||
for name in ("'vocalinux'", "'vocalinux-git'"):
|
||||
assert name in conflicts.group(1)
|
||||
|
||||
|
||||
def test_bin_pkgbuild_bundled_files_match_the_repo_copies() -> None:
|
||||
"""The launcher entry, icon and license ship next to the PKGBUILD —
|
||||
copies, not the AppImage's own .desktop (its Exec= is AppRun, meaningless
|
||||
outside the bundle). Guarded byte-for-byte so a fix to a canonical file
|
||||
cannot leave the -bin package shipping the stale one."""
|
||||
for name, canonical in (
|
||||
("vocalinux.desktop", REPO_ROOT / "vocalinux.desktop"),
|
||||
("vocalinux.svg", REPO_ROOT / "resources" / "icons" / "scalable" / "vocalinux.svg"),
|
||||
("LICENSE", REPO_ROOT / "LICENSE"),
|
||||
):
|
||||
bundled = BIN_DIR / name
|
||||
assert bundled.is_file(), f"{bundled} missing"
|
||||
assert bundled.read_bytes() == canonical.read_bytes(), (
|
||||
f"{name} drifted from {canonical.relative_to(REPO_ROOT)};" " sync the bundled copy"
|
||||
)
|
||||
assert f"'{name}'" in _bin_pkgbuild(), f"{name} not in source=()"
|
||||
desktop = (BIN_DIR / "vocalinux.desktop").read_text(encoding="utf-8")
|
||||
# The names package() links into /usr/bin and installs under hicolor.
|
||||
assert re.search(r"^Exec=vocalinux$", desktop, re.M)
|
||||
assert re.search(r"^Icon=vocalinux$", desktop, re.M)
|
||||
|
||||
|
||||
def test_bin_package_asks_the_host_only_for_what_the_bundle_lacks() -> None:
|
||||
"""The AppImage bundles CPython, GTK and the speech engines; what it
|
||||
cannot ship is the host's text-injection tools and Vulkan loader
|
||||
(docs/INSTALL.md). The required injection tools are hard depends — on
|
||||
X11 without active IBus the injector needs xdotool and on Wayland a host
|
||||
tool — while the optional fallbacks stay in optdepends. None of the
|
||||
source package's python-* depends belong here, or pacman would install a
|
||||
second Python stack next to the bundled one."""
|
||||
text = _bin_pkgbuild()
|
||||
depends = re.search(r"depends=\(\n([^)]*)\)", text)
|
||||
assert depends and "python-" not in depends.group(1)
|
||||
assert "'xdotool'" in depends.group(1)
|
||||
assert "'wtype'" in depends.group(1)
|
||||
lines = text.splitlines()
|
||||
start = next((i for i, line in enumerate(lines) if line.startswith("optdepends=(")), None)
|
||||
assert start is not None, "vocalinux-bin has no optdepends=()"
|
||||
end = next(i for i in range(start + 1, len(lines)) if lines[i].strip() == ")")
|
||||
optdepends = "\n".join(lines[start + 1 : end])
|
||||
for tool in ("ibus", "xclip", "wl-clipboard", "ydotool"):
|
||||
assert f"'{tool}" in optdepends
|
||||
assert "vulkan-icd-loader" in optdepends
|
||||
|
||||
|
||||
def test_bin_pkgbuild_tracks_the_release_tag_like_the_source_one() -> None:
|
||||
"""Same pkgver/_tag split as packaging/aur/vocalinux, real digests for
|
||||
the published AppImages, and the release workflow must bump and publish
|
||||
both — a -bin left on the last tag would serve the previous release's
|
||||
AppImage."""
|
||||
text = _bin_pkgbuild()
|
||||
assert re.search(r"^pkgver=\d", text, re.M)
|
||||
assert re.search(r"^_tag=\d", text, re.M)
|
||||
# Local builds must verify payloads against real sums, not SKIP.
|
||||
assert "'SKIP'" not in text
|
||||
release = RELEASE.read_text(encoding="utf-8")
|
||||
assert "packaging/aur/vocalinux-bin/PKGBUILD" in release, (
|
||||
"release.yml bumps the source PKGBUILD only; the -bin package would"
|
||||
" keep installing the previous release's AppImage"
|
||||
)
|
||||
# The publish step's own updpkgsums only refreshes the x86_64 array, so
|
||||
# release.yml pins both per-arch digests itself right before publishing.
|
||||
assert re.search(r"for arch in x86_64 aarch64[\s\S]*sha256sums_\$\{arch\}", release), (
|
||||
"release.yml must pin per-arch AppImage digests for vocalinux-bin — "
|
||||
"the publish action's updpkgsums only covers the runner's arch"
|
||||
)
|
||||
# The aarch64 AppImage lands in a later job; the -bin publish must wait
|
||||
# for it or the digest-pin step curls a 404 mid-release.
|
||||
assert re.search(
|
||||
r"publish-aur-bin:[\s\S]*?needs:\s*\[[^\]]*build-appimage-arm64", release
|
||||
), "publish-aur-bin must need build-appimage-arm64 so the aarch64 asset exists"
|
||||
# The -bin PKGBUILD names local sources; a lone-PKGBUILD publish leaves
|
||||
# users unable to build. asset_dir mirrors the whole directory to AUR.
|
||||
assert "asset_dir: packaging/aur/vocalinux-bin" in release
|
||||
|
||||
|
||||
def test_the_gate_builds_the_bin_package_too() -> None:
|
||||
"""The -bin package needs build validation as much as the source one:
|
||||
the gate stubs the release AppImage and runs makepkg on it."""
|
||||
script = GATE_SH.read_text(encoding="utf-8")
|
||||
assert "vocalinux-bin" in script
|
||||
assert "--skipchecksums" in script
|
||||
|
||||
Reference in New Issue
Block a user