Files
wayscriber/tools/test-release-packaging.sh
T

1778 lines
71 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
PACKAGE_CONFIG="${REPO_ROOT}/packaging/package.wayscriber.yaml"
CONFIGURATOR_PACKAGE_CONFIG="${REPO_ROOT}/packaging/package.configurator.yaml"
RELEASE_WORKFLOW="${REPO_ROOT}/.github/workflows/build-packages.yml"
CI_WORKFLOW="${REPO_ROOT}/.github/workflows/ci.yml"
PACKAGE_SCRIPT="${REPO_ROOT}/tools/package.sh"
ARCH_INSTALLER_CHECKER="${REPO_ROOT}/tools/check-arch-installer-manifest.sh"
INSTALL_SCRIPT="${REPO_ROOT}/tools/install-gtk4-layer-shell.sh"
CONFIGURATOR_INSTALL_SCRIPT="${REPO_ROOT}/tools/install-configurator.sh"
STATIC_LINK_VERIFIER="${REPO_ROOT}/tools/verify-static-gtk4-layer-shell.sh"
VERSION_CHECKER="${REPO_ROOT}/tools/check-version-consistency.sh"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "${WORK_DIR}"
}
trap cleanup EXIT
assert_contains() {
local file="$1" value="$2"
grep -Fq -- "${value}" "${file}" || {
echo "Expected ${file} to contain: ${value}" >&2
exit 1
}
}
assert_not_contains() {
local file="$1" value="$2"
if grep -Fq -- "${value}" "${file}"; then
echo "Expected ${file} not to contain: ${value}" >&2
exit 1
fi
}
# A direct configurator install is a complete desktop application install, not
# only a binary copy. Exercise every character that needs a second escaping
# pass in a quoted desktop-entry Exec value without touching the live database.
CONFIG_INSTALL_REPO="${WORK_DIR}/configurator-install-repo"
CONFIG_INSTALL_PREFIX="${WORK_DIR}/configurator % install\\root\"quote\`tick\$cash"
CONFIG_INSTALL_FAKE_BIN="${WORK_DIR}/configurator-install-fake-bin"
mkdir -p \
"${CONFIG_INSTALL_REPO}/tools" \
"${CONFIG_INSTALL_REPO}/packaging/icons" \
"${CONFIG_INSTALL_REPO}/target/release" \
"${CONFIG_INSTALL_FAKE_BIN}"
cp "${CONFIGURATOR_INSTALL_SCRIPT}" \
"${CONFIG_INSTALL_REPO}/tools/install-configurator.sh"
cp "${REPO_ROOT}/packaging/wayscriber-configurator.desktop" \
"${CONFIG_INSTALL_REPO}/packaging/wayscriber-configurator.desktop"
cp "${REPO_ROOT}/packaging/icons/wayscriber-configurator.svg" \
"${CONFIG_INSTALL_REPO}/packaging/icons/wayscriber-configurator.svg"
for size in 16 19 22 24 38 64 128; do
cp "${REPO_ROOT}/packaging/icons/wayscriber-configurator-${size}.png" \
"${CONFIG_INSTALL_REPO}/packaging/icons/wayscriber-configurator-${size}.png"
done
cat > "${CONFIG_INSTALL_REPO}/target/release/wayscriber-configurator" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "${CONFIG_INSTALL_FAKE_BIN}/cargo" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x \
"${CONFIG_INSTALL_REPO}/target/release/wayscriber-configurator" \
"${CONFIG_INSTALL_FAKE_BIN}/cargo"
(
cd "${WORK_DIR}"
PATH="${CONFIG_INSTALL_FAKE_BIN}:${PATH}" \
WAYSCRIBER_INSTALL_DIR="${CONFIG_INSTALL_PREFIX}/bin" \
bash "${CONFIG_INSTALL_REPO}/tools/install-configurator.sh" >/dev/null
)
test -x "${CONFIG_INSTALL_PREFIX}/bin/wayscriber-configurator"
CONFIG_INSTALL_DESKTOP="${CONFIG_INSTALL_PREFIX}/share/applications/wayscriber-configurator.desktop"
test -f "${CONFIG_INSTALL_DESKTOP}"
assert_contains "${CONFIG_INSTALL_DESKTOP}" \
'configurator %% install\\\\root\\"quote\\`tick\\$cash/bin/wayscriber-configurator"'
assert_contains "${CONFIG_INSTALL_DESKTOP}" \
'TryExec='"${WORK_DIR}"'/configurator % install\\root"quote`tick$cash/bin/wayscriber-configurator'
assert_contains "${CONFIG_INSTALL_DESKTOP}" "Icon=wayscriber-configurator"
for size in 16 19 22 24 38 64 128; do
test -f "${CONFIG_INSTALL_PREFIX}/share/icons/hicolor/${size}x${size}/apps/wayscriber-configurator.png"
done
test -f "${CONFIG_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps/wayscriber-configurator.svg"
test -f "${CONFIG_INSTALL_PREFIX}/share/pixmaps/wayscriber-configurator.png"
if command -v desktop-file-validate >/dev/null 2>&1; then
desktop-file-validate "${CONFIG_INSTALL_DESKTOP}"
fi
# Binary and application-data destinations are independently configurable.
# A fake sudo records escalation while safely discarding writes to a synthetic
# privileged root; writable paths must never appear in that log.
CONFIG_INSTALL_SUDO_LOG="${WORK_DIR}/configurator-install-sudo.log"
cat > "${CONFIG_INSTALL_FAKE_BIN}/sudo" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "${CONFIG_INSTALL_SUDO_LOG:?}"
if [[ "${CONFIG_INSTALL_DISCARD_SUDO:-0}" == 1 ]]; then
exit 0
fi
if [[ "$*" == *"${CONFIG_INSTALL_PRIVILEGED_ROOT:?}"* ]]; then
exit 0
fi
exec "$@"
EOF
chmod +x "${CONFIG_INSTALL_FAKE_BIN}/sudo"
CONFIG_INSTALL_PRIVILEGED_ROOT="/root/wayscriber-configurator-installer-test"
CONFIG_INSTALL_MIXED_DATA="${WORK_DIR}/configurator-mixed-data"
PATH="${CONFIG_INSTALL_FAKE_BIN}:${PATH}" \
CONFIG_INSTALL_SUDO_LOG="${CONFIG_INSTALL_SUDO_LOG}" \
CONFIG_INSTALL_PRIVILEGED_ROOT="${CONFIG_INSTALL_PRIVILEGED_ROOT}" \
WAYSCRIBER_INSTALL_DIR="${CONFIG_INSTALL_PRIVILEGED_ROOT}/bin" \
WAYSCRIBER_DATA_DIR="${CONFIG_INSTALL_MIXED_DATA}" \
bash "${CONFIG_INSTALL_REPO}/tools/install-configurator.sh" >/dev/null
assert_contains "${CONFIG_INSTALL_SUDO_LOG}" "${CONFIG_INSTALL_PRIVILEGED_ROOT}/bin"
assert_not_contains "${CONFIG_INSTALL_SUDO_LOG}" "${CONFIG_INSTALL_MIXED_DATA}"
test -f "${CONFIG_INSTALL_MIXED_DATA}/applications/wayscriber-configurator.desktop"
: > "${CONFIG_INSTALL_SUDO_LOG}"
CONFIG_INSTALL_MIXED_BIN="${WORK_DIR}/configurator-mixed-bin"
CONFIG_INSTALL_PRIVILEGED_DATA="${CONFIG_INSTALL_PRIVILEGED_ROOT}/share"
PATH="${CONFIG_INSTALL_FAKE_BIN}:${PATH}" \
CONFIG_INSTALL_SUDO_LOG="${CONFIG_INSTALL_SUDO_LOG}" \
CONFIG_INSTALL_PRIVILEGED_ROOT="${CONFIG_INSTALL_PRIVILEGED_ROOT}" \
WAYSCRIBER_INSTALL_DIR="${CONFIG_INSTALL_MIXED_BIN}" \
WAYSCRIBER_DATA_DIR="${CONFIG_INSTALL_PRIVILEGED_DATA}" \
bash "${CONFIG_INSTALL_REPO}/tools/install-configurator.sh" >/dev/null
assert_contains "${CONFIG_INSTALL_SUDO_LOG}" "${CONFIG_INSTALL_PRIVILEGED_DATA}"
assert_not_contains "${CONFIG_INSTALL_SUDO_LOG}" "${CONFIG_INSTALL_MIXED_BIN}"
test -x "${CONFIG_INSTALL_MIXED_BIN}/wayscriber-configurator"
VERSION_FAILURE_COUNT=0
expect_version_consistency_failure() {
local expected="$1" fixture_root="$2"
VERSION_FAILURE_COUNT=$((VERSION_FAILURE_COUNT + 1))
local output="${WORK_DIR}/version-failure-${VERSION_FAILURE_COUNT}.log"
set +e
(
cd "${fixture_root}"
bash tools/check-version-consistency.sh
) >"${output}" 2>&1
local status=$?
set -e
if [[ ${status} -eq 0 ]]; then
echo "Expected version consistency check to fail: ${expected}" >&2
exit 1
fi
assert_contains "${output}" "${expected}"
}
# Trimming trailing separators must not turn the filesystem root into the
# invocation directory. Discard the deliberately privileged writes after sudo
# records their normalized destinations.
: > "${CONFIG_INSTALL_SUDO_LOG}"
CONFIG_INSTALL_ROOT_CWD="${WORK_DIR}/configurator-root-cwd"
CONFIG_INSTALL_ROOT_DATA="${WORK_DIR}/configurator-root-data"
mkdir -p "${CONFIG_INSTALL_ROOT_CWD}"
(
cd "${CONFIG_INSTALL_ROOT_CWD}"
PATH="${CONFIG_INSTALL_FAKE_BIN}:${PATH}" \
CONFIG_INSTALL_SUDO_LOG="${CONFIG_INSTALL_SUDO_LOG}" \
CONFIG_INSTALL_PRIVILEGED_ROOT="${CONFIG_INSTALL_PRIVILEGED_ROOT}" \
CONFIG_INSTALL_DISCARD_SUDO=1 \
WAYSCRIBER_INSTALL_DIR=/ \
WAYSCRIBER_DATA_DIR="${CONFIG_INSTALL_ROOT_DATA}" \
bash "${CONFIG_INSTALL_REPO}/tools/install-configurator.sh" >/dev/null
)
assert_contains "${CONFIG_INSTALL_SUDO_LOG}" "install -d /"
CONFIG_INSTALL_ROOT_DESKTOP="${CONFIG_INSTALL_ROOT_DATA}/applications/wayscriber-configurator.desktop"
assert_contains "${CONFIG_INSTALL_ROOT_DESKTOP}" 'Exec="/wayscriber-configurator"'
assert_contains "${CONFIG_INSTALL_ROOT_DESKTOP}" 'TryExec=/wayscriber-configurator'
: > "${CONFIG_INSTALL_SUDO_LOG}"
CONFIG_INSTALL_ROOT_BIN="${WORK_DIR}/configurator-root-bin"
(
cd "${CONFIG_INSTALL_ROOT_CWD}"
PATH="${CONFIG_INSTALL_FAKE_BIN}:${PATH}" \
CONFIG_INSTALL_SUDO_LOG="${CONFIG_INSTALL_SUDO_LOG}" \
CONFIG_INSTALL_PRIVILEGED_ROOT="${CONFIG_INSTALL_PRIVILEGED_ROOT}" \
CONFIG_INSTALL_DISCARD_SUDO=1 \
WAYSCRIBER_INSTALL_DIR="${CONFIG_INSTALL_ROOT_BIN}" \
WAYSCRIBER_DATA_DIR=/ \
bash "${CONFIG_INSTALL_REPO}/tools/install-configurator.sh" >/dev/null
)
test -x "${CONFIG_INSTALL_ROOT_BIN}/wayscriber-configurator"
assert_contains "${CONFIG_INSTALL_SUDO_LOG}" "/applications/wayscriber-configurator.desktop"
test ! -e "${CONFIG_INSTALL_ROOT_CWD}/applications/wayscriber-configurator.desktop"
# Prebuilt deb/rpm packages must declare the supported ABI floors, while the
# statically embedded layer-shell implementation must not remain a dependency.
sed -n '/^overrides:/,$p' "${PACKAGE_CONFIG}" > "${WORK_DIR}/package-overrides.yml"
assert_contains "${PACKAGE_CONFIG}" "- libc6 (>= 2.39)"
assert_contains "${PACKAGE_CONFIG}" "- libgtk-4-1 (>= 4.12)"
assert_contains "${PACKAGE_CONFIG}" "- glibc >= 2.39"
assert_contains "${PACKAGE_CONFIG}" "- gtk4 >= 4.12"
assert_not_contains "${WORK_DIR}/package-overrides.yml" "- libgtk4-layer-shell0"
assert_not_contains "${WORK_DIR}/package-overrides.yml" "- gtk4-layer-shell"
assert_contains "${CONFIGURATOR_PACKAGE_CONFIG}" "- libc6 (>= 2.39)"
assert_contains "${CONFIGURATOR_PACKAGE_CONFIG}" "- glibc >= 2.39"
assert_contains "${CONFIGURATOR_PACKAGE_CONFIG}" "- libadwaita-1-0 (>= 1.4)"
assert_contains "${CONFIGURATOR_PACKAGE_CONFIG}" "- libadwaita >= 1.4"
# One supported libadwaita floor governs compilation and every package
# channel. Exercise the real consistency checker, then mutation-check each
# independently editable metadata surface so a partial floor bump cannot pass.
LIBADWAITA_FLOOR_REPO="${WORK_DIR}/libadwaita-floor-repo"
mkdir -p \
"${LIBADWAITA_FLOOR_REPO}/.github/workflows" \
"${LIBADWAITA_FLOOR_REPO}/configurator" \
"${LIBADWAITA_FLOOR_REPO}/packaging" \
"${LIBADWAITA_FLOOR_REPO}/tools"
cp "${REPO_ROOT}/Cargo.toml" \
"${REPO_ROOT}/Cargo.lock" \
"${REPO_ROOT}/README.md" \
"${REPO_ROOT}/flake.nix" \
"${REPO_ROOT}/global.json" \
"${LIBADWAITA_FLOOR_REPO}/"
cp "${REPO_ROOT}/configurator/Cargo.toml" \
"${LIBADWAITA_FLOOR_REPO}/configurator/Cargo.toml"
cp "${REPO_ROOT}/packaging/PKGBUILD" \
"${REPO_ROOT}/packaging/.SRCINFO" \
"${REPO_ROOT}/packaging/package.configurator.yaml" \
"${LIBADWAITA_FLOOR_REPO}/packaging/"
cp "${VERSION_CHECKER}" \
"${LIBADWAITA_FLOOR_REPO}/tools/check-version-consistency.sh"
cp "${RELEASE_WORKFLOW}" \
"${LIBADWAITA_FLOOR_REPO}/.github/workflows/build-packages.yml"
cp "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
(
cd "${LIBADWAITA_FLOOR_REPO}"
bash tools/check-version-consistency.sh >/dev/null
)
printf '%s\n' '{"sdk":{"version":"11.0.100-rc.1.26425.128","rollForward":"disable","allowPrerelease":true}}' \
> "${LIBADWAITA_FLOOR_REPO}/global.json"
(
cd "${LIBADWAITA_FLOOR_REPO}"
bash tools/check-version-consistency.sh >/dev/null
)
printf '%s\n' '{"sdk":{}}' > "${LIBADWAITA_FLOOR_REPO}/global.json"
expect_version_consistency_failure \
"global.json SDK metadata is invalid" \
"${LIBADWAITA_FLOOR_REPO}"
printf '%s\n' '{"sdk":{"version":"11.0.100-rc.1.26425.127","rollForward":"disable","allowPrerelease":true}}' \
> "${LIBADWAITA_FLOOR_REPO}/global.json"
expect_version_consistency_failure \
"global.json SDK: expected 11.0.100-rc.1.26425.128, got 11.0.100-rc.1.26425.127" \
"${LIBADWAITA_FLOOR_REPO}"
printf '%s\n' '{"sdk":{"version":"11.0.100-rc.1.26425.128","rollForward":"latestPatch","allowPrerelease":false}}' \
> "${LIBADWAITA_FLOOR_REPO}/global.json"
expect_version_consistency_failure \
"global.json SDK rollForward must be disable" \
"${LIBADWAITA_FLOOR_REPO}"
expect_version_consistency_failure \
"global.json SDK allowPrerelease must be true" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/global.json" "${LIBADWAITA_FLOOR_REPO}/global.json"
sed -i 's/features = \["v1_4"\]/features = ["v1_5"]/' \
"${LIBADWAITA_FLOOR_REPO}/configurator/Cargo.toml"
expect_version_consistency_failure \
"configurator/Cargo.toml libadwaita features: expected ['v1_4'], got ['v1_5']" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/configurator/Cargo.toml" \
"${LIBADWAITA_FLOOR_REPO}/configurator/Cargo.toml"
sed -i 's/libadwaita-1-0 (>= 1.4)/libadwaita-1-0 (>= 1.5)/' \
"${LIBADWAITA_FLOOR_REPO}/packaging/package.configurator.yaml"
expect_version_consistency_failure \
"configurator deb libadwaita floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/packaging/package.configurator.yaml" \
"${LIBADWAITA_FLOOR_REPO}/packaging/package.configurator.yaml"
sed -i 's/libadwaita >= 1.4/libadwaita >= 1.5/' \
"${LIBADWAITA_FLOOR_REPO}/packaging/package.configurator.yaml"
expect_version_consistency_failure \
"configurator rpm libadwaita floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/packaging/package.configurator.yaml" \
"${LIBADWAITA_FLOOR_REPO}/packaging/package.configurator.yaml"
sed -i 's/libadwaita>=1.4/libadwaita>=1.5/' \
"${LIBADWAITA_FLOOR_REPO}/packaging/PKGBUILD"
expect_version_consistency_failure \
"packaging/PKGBUILD libadwaita floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/packaging/PKGBUILD" \
"${LIBADWAITA_FLOOR_REPO}/packaging/PKGBUILD"
sed -i 's/libadwaita>=1.4/libadwaita>=1.5/' \
"${LIBADWAITA_FLOOR_REPO}/packaging/.SRCINFO"
expect_version_consistency_failure \
"packaging/.SRCINFO libadwaita floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/packaging/.SRCINFO" \
"${LIBADWAITA_FLOOR_REPO}/packaging/.SRCINFO"
sed -i \
"s/ensure_runtime_dependency 'libadwaita>=1.4'/ensure_runtime_dependency 'libadwaita>=1.5'/" \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
expect_version_consistency_failure \
"AUR updater generated libadwaita floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
sed -i '/grep -Eq.*libadwaita/ s/libadwaita>=1.4/libadwaita>=1.5/' \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
expect_version_consistency_failure \
"AUR updater PKGBUILD validation floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
cp "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
sed -i '/grep -Fxq.*depends = libadwaita/ s/libadwaita>=1.4/libadwaita>=1.5/' \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
expect_version_consistency_failure \
"AUR updater .SRCINFO validation floor: expected 1.4, got 1.5" \
"${LIBADWAITA_FLOOR_REPO}"
# Updating every metadata floor is still incomplete until the package runner
# moves to a reviewed release-platform contract that supports the new ABI.
cp "${REPO_ROOT}/packaging/.SRCINFO" \
"${LIBADWAITA_FLOOR_REPO}/packaging/.SRCINFO"
sed -i 's/supported_libadwaita_floor = "1.4"/supported_libadwaita_floor = "1.7"/' \
"${LIBADWAITA_FLOOR_REPO}/tools/check-version-consistency.sh"
sed -i 's/features = \["v1_4"\]/features = ["v1_7"]/' \
"${LIBADWAITA_FLOOR_REPO}/configurator/Cargo.toml"
sed -i 's/1\.4/1.7/g' \
"${LIBADWAITA_FLOOR_REPO}/packaging/PKGBUILD" \
"${LIBADWAITA_FLOOR_REPO}/packaging/.SRCINFO" \
"${LIBADWAITA_FLOOR_REPO}/packaging/package.configurator.yaml" \
"${LIBADWAITA_FLOOR_REPO}/.github/workflows/build-packages.yml"
cp "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
sed -i 's/1\.4/1.7/g' \
"${LIBADWAITA_FLOOR_REPO}/tools/update-aur-from-manifest.sh"
expect_version_consistency_failure \
"release package runner ubuntu-24.04 libadwaita floor: expected 1.4, got 1.7" \
"${LIBADWAITA_FLOOR_REPO}"
# Release jobs must not silently raise the glibc floor when ubuntu-latest
# changes; the package job is the binary floor's defining runner.
sed -n '/^ package:/,/^ package-repos:/p' "${RELEASE_WORKFLOW}" \
> "${WORK_DIR}/release-package-job.yml"
assert_contains "${WORK_DIR}/release-package-job.yml" "runs-on: ubuntu-24.04"
assert_not_contains "${RELEASE_WORKFLOW}" "runs-on: ubuntu-latest"
# Recipe URLs must not become public while GitHub asset upload is still running.
sed -n '/^ aur:/,/^ [a-z][a-z-]*:/p' "${RELEASE_WORKFLOW}" \
> "${WORK_DIR}/release-aur-job.yml"
assert_contains "${WORK_DIR}/release-aur-job.yml" 'needs: [package, release]'
assert_contains "${WORK_DIR}/release-package-job.yml" 'package verify-artifacts --artifact-root dist'
assert_contains "${WORK_DIR}/release-package-job.yml" 'wayscriber-configurator-v${{ steps.meta.outputs.version }}-linux-x86_64.tar.gz'
assert_contains "${WORK_DIR}/release-package-job.yml" \
'package smoke-ubuntu --artifact-root dist'
assert_contains "${WORK_DIR}/release-package-job.yml" "Check direct Arch installer compatibility"
assert_contains "${WORK_DIR}/release-package-job.yml" "package check-live-arch-installer"
assert_contains "${WORK_DIR}/release-package-job.yml" 'wayscriber-v${{ steps.meta.outputs.version }}-linux-x86_64.tar.gz'
assert_not_contains "${RELEASE_WORKFLOW}" 'bash '
assert_contains "${REPO_ROOT}/README.md" "--replace-other"
assert_contains "${REPO_ROOT}/README.md" "--no-restart"
assert_contains "${REPO_ROOT}/README.md" "--remove-unmanaged-usr"
test -x "${ARCH_INSTALLER_CHECKER}"
assert_contains "${ARCH_INSTALLER_CHECKER}" "The installer is parsed as data and is not run."
# The deployed installer is parsed as a strict data contract. Unsupported
# shell syntax must fail instead of silently omitting a required release file.
ARCH_INSTALLER_FIXTURE_ROOT="wayscriber-v0.0.0-linux-x86_64"
ARCH_INSTALLER_FIXTURE_STAGE="${WORK_DIR}/arch-installer-stage"
ARCH_INSTALLER_FIXTURE_ARCHIVE="${WORK_DIR}/arch-installer.tar.gz"
ARCH_INSTALLER_VALID_FIXTURE="${WORK_DIR}/arch-installer-valid.sh"
ARCH_INSTALLER_MALFORMED_FIXTURE="${WORK_DIR}/arch-installer-malformed.sh"
ARCH_INSTALLER_MALFORMED_OUTPUT="${WORK_DIR}/arch-installer-malformed-output"
ARCH_INSTALLER_SERVICE_OUTPUT="${WORK_DIR}/arch-installer-service-output"
mkdir -p \
"${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/bin" \
"${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/lib/systemd/user"
touch "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/bin/wayscriber"
chmod 0755 "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/bin/wayscriber"
cat > "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/lib/systemd/user/wayscriber.service" <<'EOF'
[Service]
ExecStart="/usr/bin/wayscriber" --daemon
EOF
chmod 0644 "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/lib/systemd/user/wayscriber.service"
tar -czf "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" \
-C "${ARCH_INSTALLER_FIXTURE_STAGE}" "${ARCH_INSTALLER_FIXTURE_ROOT}"
cat > "${ARCH_INSTALLER_VALID_FIXTURE}" <<'EOF'
#!/bin/sh
# ARCH_INSTALL_MANIFEST_BEGIN
release_manifest() {
printf '%s\n' \
'0755 bin/wayscriber' \
'0644 lib/systemd/user/wayscriber.service'
}
# ARCH_INSTALL_MANIFEST_END
EOF
"${ARCH_INSTALLER_CHECKER}" \
--installer "${ARCH_INSTALLER_VALID_FIXTURE}" \
--archive "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" >/dev/null
printf '%s\n' 'ExecStartPost=/usr/bin/env \' '# ignored by systemd' ' wayscriber --active' \
>> "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/lib/systemd/user/wayscriber.service"
tar -czf "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" \
-C "${ARCH_INSTALLER_FIXTURE_STAGE}" "${ARCH_INSTALLER_FIXTURE_ROOT}"
set +e
"${ARCH_INSTALLER_CHECKER}" \
--installer "${ARCH_INSTALLER_VALID_FIXTURE}" \
--archive "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" \
>"${ARCH_INSTALLER_SERVICE_OUTPUT}" 2>&1
ARCH_INSTALLER_SERVICE_STATUS=$?
set -e
if [[ ${ARCH_INSTALLER_SERVICE_STATUS} -eq 0 ]]; then
echo "Expected an alternate Wayscriber service command to fail" >&2
exit 1
fi
assert_contains "${ARCH_INSTALLER_SERVICE_OUTPUT}" \
"release user service is incompatible"
cat > "${ARCH_INSTALLER_FIXTURE_STAGE}/${ARCH_INSTALLER_FIXTURE_ROOT}/usr/lib/systemd/user/wayscriber.service" <<'EOF'
[Service]
ExecStart="/usr/bin/wayscriber" --daemon
EOF
tar -czf "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" \
-C "${ARCH_INSTALLER_FIXTURE_STAGE}" "${ARCH_INSTALLER_FIXTURE_ROOT}"
cat > "${ARCH_INSTALLER_MALFORMED_FIXTURE}" <<'EOF'
#!/bin/sh
# ARCH_INSTALL_MANIFEST_BEGIN
release_manifest() {
printf '%s\n' \
'0755 bin/wayscriber' \
'0644 lib/systemd/user/wayscriber.service' \
"0644 share/foo"
}
# ARCH_INSTALL_MANIFEST_END
EOF
set +e
"${ARCH_INSTALLER_CHECKER}" \
--installer "${ARCH_INSTALLER_MALFORMED_FIXTURE}" \
--archive "${ARCH_INSTALLER_FIXTURE_ARCHIVE}" \
>"${ARCH_INSTALLER_MALFORMED_OUTPUT}" 2>&1
ARCH_INSTALLER_MALFORMED_STATUS=$?
set -e
if [[ ${ARCH_INSTALLER_MALFORMED_STATUS} -eq 0 ]]; then
echo "Expected unsupported installer manifest syntax to fail" >&2
exit 1
fi
assert_contains "${ARCH_INSTALLER_MALFORMED_OUTPUT}" \
"unsupported installer manifest syntax"
# Ordinary CI exercises the dynamic source-build path, while a dedicated step
# checks the static release path.
assert_contains "${CI_WORKFLOW}" "ci prepare-gtk4-layer-shell --library-mode both"
assert_contains "${CI_WORKFLOW}" "Check dynamic gtk4-layer-shell linkage"
assert_contains "${CI_WORKFLOW}" "Check static gtk4-layer-shell linkage"
if grep -Eq 'SYSTEM_DEPS_GTK4_LAYER_SHELL_0_LINK.*GITHUB_ENV|GITHUB_ENV.*SYSTEM_DEPS_GTK4_LAYER_SHELL_0_LINK' \
"${CI_WORKFLOW}"; then
echo "Static gtk4-layer-shell linkage must not be exported globally in CI" >&2
exit 1
fi
# Release builds use the private static archive. Reusable prefixes must carry
# the exact commit/checksum stamp rather than matching on version alone.
assert_contains "${PACKAGE_SCRIPT}" 'SYSTEM_DEPS_GTK4_LAYER_SHELL_0_LINK=static'
assert_contains "${PACKAGE_SCRIPT}" 'verify-static-gtk4-layer-shell.sh'
assert_contains "${INSTALL_SCRIPT}" 'PIN_STAMP='
assert_contains "${INSTALL_SCRIPT}" 'commit=%s'
assert_contains "${INSTALL_SCRIPT}" 'archive_sha256=%s'
# package.sh's documented default discovers the Cargo version itself. Exercise
# that public path with a fake nfpm and require the resolved version to cross
# the process boundary into nfpm's environment.
PACKAGE_VERSION_REPO="${WORK_DIR}/package-version-repo"
PACKAGE_VERSION_FAKE_BIN="${WORK_DIR}/package-version-fake-bin"
PACKAGE_VERSION_ARTIFACTS="${WORK_DIR}/package-version-artifacts"
mkdir -p "${PACKAGE_VERSION_REPO}/tools" \
"${PACKAGE_VERSION_REPO}/packaging/icons" \
"${PACKAGE_VERSION_REPO}/packaging/licenses" \
"${PACKAGE_VERSION_REPO}/target/release" \
"${PACKAGE_VERSION_FAKE_BIN}"
cp "${PACKAGE_SCRIPT}" "${PACKAGE_VERSION_REPO}/tools/package.sh"
touch "${PACKAGE_VERSION_REPO}/Cargo.toml" \
"${PACKAGE_VERSION_REPO}/README.md" \
"${PACKAGE_VERSION_REPO}/config.example.toml" \
"${PACKAGE_VERSION_REPO}/LICENSE" \
"${PACKAGE_VERSION_REPO}/packaging/package.wayscriber.yaml" \
"${PACKAGE_VERSION_REPO}/packaging/package.configurator.yaml" \
"${PACKAGE_VERSION_REPO}/packaging/wayscriber.service" \
"${PACKAGE_VERSION_REPO}/packaging/wayscriber.desktop" \
"${PACKAGE_VERSION_REPO}/packaging/wayscriber-configurator.desktop" \
"${PACKAGE_VERSION_REPO}/packaging/licenses/gtk4-layer-shell.LICENSE" \
"${PACKAGE_VERSION_REPO}/packaging/icons/wayscriber.svg" \
"${PACKAGE_VERSION_REPO}/packaging/icons/wayscriber-symbolic.svg" \
"${PACKAGE_VERSION_REPO}/packaging/icons/wayscriber-configurator.svg"
for size in 16 19 22 24 38 64 128; do
touch "${PACKAGE_VERSION_REPO}/packaging/icons/wayscriber-${size}.png"
done
for size in 24 64 128; do
touch "${PACKAGE_VERSION_REPO}/packaging/icons/wayscriber-configurator-${size}.png"
done
cat > "${PACKAGE_VERSION_REPO}/tools/verify-static-gtk4-layer-shell.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "${PACKAGE_VERSION_REPO}/target/release/wayscriber" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "${PACKAGE_VERSION_REPO}/target/release/wayscriber-configurator" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
cat > "${PACKAGE_VERSION_FAKE_BIN}/cargo" <<'EOF'
#!/usr/bin/env bash
[[ "${1:-}" == "metadata" ]] || {
echo "Unexpected cargo arguments: $*" >&2
exit 2
}
[[ "${PWD}" == "${PACKAGE_VERSION_EXPECTED_ROOT:?}" ]] || {
echo "package.sh ran cargo metadata from ${PWD}, expected ${PACKAGE_VERSION_EXPECTED_ROOT}" >&2
exit 3
}
if [[ "${PACKAGE_VERSION_MISSING:-0}" == 1 ]]; then
printf '%s\n' '{"packages":[]}'
else
printf '%s\n' '{"packages":[{"name":"wayscriber","version":"0.9.22"}]}'
fi
EOF
cat > "${PACKAGE_VERSION_FAKE_BIN}/readelf" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' ' 0x0010: Name: GLIBC_2.39 Flags: none Version: 4'
EOF
cat > "${PACKAGE_VERSION_FAKE_BIN}/nfpm" <<'EOF'
#!/usr/bin/env bash
[[ -n "${VERSION:-}" ]] || {
echo "nfpm did not receive the resolved package version" >&2
exit 91
}
target=""
while [[ $# -gt 0 ]]; do
if [[ "$1" == "--target" ]]; then
target="$2"
break
fi
shift
done
[[ -n "${target}" ]] || {
echo "nfpm did not receive --target" >&2
exit 92
}
mkdir -p "$(dirname "${target}")"
package_kind="main"
[[ "${target}" == *wayscriber-configurator* ]] && package_kind="configurator"
if [[ "${PACKAGE_NFPM_FAIL:-}" == "${package_kind}" ]]; then
printf '%s\n' 'partial package' > "${target}"
echo "PACKAGE_TEST_NFPM_FAILURE" >&2
exit 91
fi
printf '%s\n' "${VERSION}" > "${target}"
EOF
cat > "${PACKAGE_VERSION_FAKE_BIN}/tar" <<'EOF'
#!/usr/bin/env bash
target=""
while [[ $# -gt 0 ]]; do
if [[ "$1" == "-czf" ]]; then
target="$2"
break
fi
shift
done
[[ -n "${target}" ]] || {
echo "tar did not receive -czf" >&2
exit 92
}
mkdir -p "$(dirname "${target}")"
package_kind="main"
[[ "${target}" == *wayscriber-configurator* ]] && package_kind="configurator"
if [[ "${PACKAGE_TAR_FAIL:-}" == "${package_kind}" ]]; then
printf '%s\n' 'partial archive' > "${target}"
echo "PACKAGE_TEST_TAR_FAILURE" >&2
exit 93
fi
printf '%s\n' 'complete archive' > "${target}"
EOF
cat > "${PACKAGE_VERSION_FAKE_BIN}/cp" <<'EOF'
#!/usr/bin/env bash
package_kind=""
case "${1:-}" in
*/target/release/wayscriber) package_kind="main" ;;
*/target/release/wayscriber-configurator) package_kind="configurator" ;;
esac
if [[ -n "${package_kind}" && "${PACKAGE_COPY_FAIL:-}" == "${package_kind}" ]]; then
echo "PACKAGE_TEST_COPY_FAILURE" >&2
exit 95
fi
command -p cp "$@"
EOF
chmod +x "${PACKAGE_VERSION_REPO}/tools/verify-static-gtk4-layer-shell.sh" \
"${PACKAGE_VERSION_REPO}/target/release/wayscriber" \
"${PACKAGE_VERSION_REPO}/target/release/wayscriber-configurator" \
"${PACKAGE_VERSION_FAKE_BIN}/cargo" \
"${PACKAGE_VERSION_FAKE_BIN}/readelf" \
"${PACKAGE_VERSION_FAKE_BIN}/nfpm" \
"${PACKAGE_VERSION_FAKE_BIN}/tar" \
"${PACKAGE_VERSION_FAKE_BIN}/cp"
expect_package_failure() {
local expected_message="$1"
local output_file="${WORK_DIR}/package-failure-output"
shift
set +e
"$@" >"${output_file}" 2>&1
local test_status=$?
set -e
if [[ ${test_status} -eq 0 ]]; then
echo "Expected package.sh to fail with: ${expected_message}" >&2
cat "${output_file}" >&2
exit 1
fi
if ! grep -Fq -- "${expected_message}" "${output_file}"; then
echo "package.sh failed for the wrong reason; expected: ${expected_message}" >&2
cat "${output_file}" >&2
exit 1
fi
}
(
cd "${WORK_DIR}"
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_VERSION_EXPECTED_ROOT="${PACKAGE_VERSION_REPO}" \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--formats deb \
--artifact-root "${PACKAGE_VERSION_ARTIFACTS}" \
--skip-build \
--no-strip >/dev/null
)
[[ "$(cat "${PACKAGE_VERSION_ARTIFACTS}/wayscriber-amd64.deb")" == "0.9.22" ]] || {
echo "package.sh did not pass its auto-detected version to nfpm" >&2
exit 1
}
[[ "$(cat "${PACKAGE_VERSION_ARTIFACTS}/wayscriber-configurator-amd64.deb")" == "0.9.22" ]] || {
echo "package.sh did not pass its auto-detected version to configurator nfpm" >&2
exit 1
}
PACKAGE_OVERRIDE_ARTIFACTS="${WORK_DIR}/package-override-artifacts"
(
cd "${WORK_DIR}"
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_VERSION_EXPECTED_ROOT="${PACKAGE_VERSION_REPO}" \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 9.8.7 \
--formats deb \
--artifact-root "${PACKAGE_OVERRIDE_ARTIFACTS}" \
--skip-build \
--no-strip >/dev/null
)
[[ "$(cat "${PACKAGE_OVERRIDE_ARTIFACTS}/wayscriber-amd64.deb")" == "9.8.7" ]] || {
echo "package.sh did not pass its explicit version override to nfpm" >&2
exit 1
}
[[ "$(cat "${PACKAGE_OVERRIDE_ARTIFACTS}/wayscriber-configurator-amd64.deb")" == "9.8.7" ]] || {
echo "package.sh did not pass its explicit version override to configurator nfpm" >&2
exit 1
}
PACKAGE_MISSING_VERSION_ARTIFACTS="${WORK_DIR}/package-missing-version-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure "Could not resolve the package version" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_VERSION_EXPECTED_ROOT="${PACKAGE_VERSION_REPO}" \
PACKAGE_VERSION_MISSING=1 \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--formats deb \
--artifact-root "${PACKAGE_MISSING_VERSION_ARTIFACTS}" \
--skip-build \
--no-strip
)
PACKAGE_NFPM_FAILURE_ARTIFACTS="${WORK_DIR}/package-nfpm-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure \
"Failed to build deb package: ${PACKAGE_NFPM_FAILURE_ARTIFACTS}/wayscriber-amd64.deb" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_NFPM_FAIL=main \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats deb \
--artifact-root "${PACKAGE_NFPM_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip
)
PACKAGE_CONFIGURATOR_NFPM_FAILURE_ARTIFACTS="${WORK_DIR}/package-configurator-nfpm-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure \
"Failed to build deb package: ${PACKAGE_CONFIGURATOR_NFPM_FAILURE_ARTIFACTS}/wayscriber-configurator-amd64.deb" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_NFPM_FAIL=configurator \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats deb \
--artifact-root "${PACKAGE_CONFIGURATOR_NFPM_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip
)
PACKAGE_TAR_FAILURE_ARTIFACTS="${WORK_DIR}/package-tar-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure \
"Failed to build tarball: ${PACKAGE_TAR_FAILURE_ARTIFACTS}/wayscriber-v0.9.22-linux-x86_64.tar.gz" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_TAR_FAIL=main \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats tar \
--artifact-root "${PACKAGE_TAR_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip \
--no-configurator
)
PACKAGE_CONFIGURATOR_TAR_FAILURE_ARTIFACTS="${WORK_DIR}/package-configurator-tar-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure \
"Failed to build tarball: ${PACKAGE_CONFIGURATOR_TAR_FAILURE_ARTIFACTS}/wayscriber-configurator-v0.9.22-linux-x86_64.tar.gz" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_TAR_FAIL=configurator \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats tar \
--artifact-root "${PACKAGE_CONFIGURATOR_TAR_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip
)
PACKAGE_MAIN_COPY_FAILURE_ARTIFACTS="${WORK_DIR}/package-main-copy-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure "PACKAGE_TEST_COPY_FAILURE" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_COPY_FAIL=main \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats tar \
--artifact-root "${PACKAGE_MAIN_COPY_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip \
--no-configurator
)
PACKAGE_CONFIGURATOR_COPY_FAILURE_ARTIFACTS="${WORK_DIR}/package-configurator-copy-failure-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure "PACKAGE_TEST_COPY_FAILURE" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
PACKAGE_COPY_FAIL=configurator \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats tar \
--artifact-root "${PACKAGE_CONFIGURATOR_COPY_FAILURE_ARTIFACTS}" \
--skip-build \
--no-strip
)
# PACKAGE_CONFIGURATOR=1 promises every requested configurator artifact. A
# missing executable must fail before packaging; --no-configurator is the
# explicit opt-out.
rm -f "${PACKAGE_VERSION_REPO}/target/release/wayscriber-configurator"
PACKAGE_MISSING_CONFIGURATOR_ARTIFACTS="${WORK_DIR}/package-missing-configurator-artifacts"
(
cd "${WORK_DIR}"
expect_package_failure \
"Missing release binary: ${PACKAGE_VERSION_REPO}/target/release/wayscriber-configurator" \
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats tar \
--artifact-root "${PACKAGE_MISSING_CONFIGURATOR_ARTIFACTS}" \
--skip-build \
--no-strip
)
PACKAGE_NO_CONFIGURATOR_ARTIFACTS="${WORK_DIR}/package-no-configurator-artifacts"
(
cd "${WORK_DIR}"
env -u VERSION \
PATH="${PACKAGE_VERSION_FAKE_BIN}:${PATH}" \
bash "${PACKAGE_VERSION_REPO}/tools/package.sh" \
--version 0.9.22 \
--formats deb \
--artifact-root "${PACKAGE_NO_CONFIGURATOR_ARTIFACTS}" \
--skip-build \
--no-strip \
--no-configurator >/dev/null
)
[[ -f "${PACKAGE_NO_CONFIGURATOR_ARTIFACTS}/wayscriber-amd64.deb" ]] || {
echo "--no-configurator did not produce the requested main package" >&2
exit 1
}
[[ ! -e "${PACKAGE_NO_CONFIGURATOR_ARTIFACTS}/wayscriber-configurator-amd64.deb" ]] || {
echo "--no-configurator unexpectedly produced a configurator package" >&2
exit 1
}
# A private cache created before the stamp recorded library mode must be
# rebuilt even when leftover shared/static artifacts satisfy the new request.
INSTALLER_FAKE_BIN="${WORK_DIR}/installer-fake-bin"
INSTALLER_PREFIX="${WORK_DIR}/installer-prefix"
INSTALLER_STAMP="${INSTALLER_PREFIX}/share/wayscriber/build-deps/gtk4-layer-shell.pin"
mkdir -p "${INSTALLER_FAKE_BIN}" "${INSTALLER_PREFIX}/lib" "$(dirname "${INSTALLER_STAMP}")"
touch "${INSTALLER_PREFIX}/lib/libgtk4-layer-shell.a" \
"${INSTALLER_PREFIX}/lib/libgtk4-layer-shell.so"
cat > "${INSTALLER_FAKE_BIN}/pkg-config" <<'EOF'
#!/usr/bin/env bash
case "$*" in
*--atleast-version=*) exit 0 ;;
*--variable=libdir*) printf '%s\n' "${INSTALLER_TEST_LIBDIR:?}" ;;
*--modversion*) printf '%s\n' '1.3.0' ;;
*) echo "Unexpected pkg-config arguments: $*" >&2; exit 2 ;;
esac
EOF
cat > "${INSTALLER_FAKE_BIN}/curl" <<'EOF'
#!/usr/bin/env bash
echo 'INSTALLER_TEST_REBUILD_REQUIRED' >&2
exit 97
EOF
cat > "${INSTALLER_FAKE_BIN}/meson" <<'EOF'
#!/usr/bin/env bash
echo 'Meson must not run in installer cache tests' >&2
exit 98
EOF
chmod +x "${INSTALLER_FAKE_BIN}/pkg-config" \
"${INSTALLER_FAKE_BIN}/curl" \
"${INSTALLER_FAKE_BIN}/meson"
run_installer_cache_check() {
PATH="${INSTALLER_FAKE_BIN}:${PATH}" \
INSTALLER_TEST_LIBDIR="${INSTALLER_PREFIX}/lib" \
GTK4_LAYER_SHELL_PREFIX="${INSTALLER_PREFIX}" \
GTK4_LAYER_SHELL_SYSTEM_PREFIX="${2:-/usr}" \
GTK4_LAYER_SHELL_LIBRARY_MODE="$1" \
bash "${INSTALL_SCRIPT}"
}
expect_installer_rebuild() {
local description="$1" mode="$2" system_prefix="${3:-/usr}" output status
set +e
output="$(run_installer_cache_check "${mode}" "${system_prefix}" 2>&1)"
status=$?
set -e
if [[ "${status}" -eq 0 ]]; then
echo "Installer unexpectedly reused ${description}" >&2
exit 1
fi
grep -Fq 'INSTALLER_TEST_REBUILD_REQUIRED' <<< "${output}" || {
echo "Installer rejected ${description} for an unrelated reason:" >&2
echo "${output}" >&2
exit 1
}
}
expect_installer_skip() {
local description="$1" mode="$2" system_prefix="${3:-/usr}" output
output="$(run_installer_cache_check "${mode}" "${system_prefix}" 2>&1)" || {
echo "Installer failed to reuse ${description}:" >&2
echo "${output}" >&2
exit 1
}
grep -Fq 'skipping build' <<< "${output}" || {
echo "Installer did not report reusing ${description}" >&2
exit 1
}
if grep -Fq 'INSTALLER_TEST_REBUILD_REQUIRED' <<< "${output}"; then
echo "Installer downloaded while reusing ${description}" >&2
exit 1
fi
}
write_installer_stamp() {
cat > "${INSTALLER_STAMP}" <<EOF
version=1.3.0
commit=1c963c51514581c41b9bdae08cdf69171265cdda
archive_sha256=22be5f5edf487cfb87266f0e71c400b11322082a4dc99832e5a54a4fca3d5a7c
library_mode=$1
EOF
}
cat > "${INSTALLER_STAMP}" <<'EOF'
version=1.3.0
commit=1c963c51514581c41b9bdae08cdf69171265cdda
archive_sha256=22be5f5edf487cfb87266f0e71c400b11322082a4dc99832e5a54a4fca3d5a7c
EOF
expect_installer_rebuild "a mode-less private cache" shared
write_installer_stamp static
expect_installer_skip "a matching static private cache" static
expect_installer_rebuild "a static private cache requested as shared" shared
write_installer_stamp shared
expect_installer_skip "a matching shared private cache" shared
rm -f "${INSTALLER_STAMP}"
expect_installer_rebuild "an unstamped private cache" shared
expect_installer_skip "an unstamped system-managed cache" shared "${INSTALLER_PREFIX}"
# Exercise the linkage verifier itself, including every exact shim name. The
# shorter marshal names must not pass by matching longer symbol substrings.
FAKE_BIN="${WORK_DIR}/fake-bin"
READELF_OK="${WORK_DIR}/readelf-ok.txt"
NM_OK="${WORK_DIR}/nm-ok.txt"
mkdir -p "${FAKE_BIN}"
cat > "${FAKE_BIN}/readelf" <<'EOF'
#!/usr/bin/env bash
cat "${VERIFY_TEST_READELF_OUTPUT:?}"
EOF
cat > "${FAKE_BIN}/nm" <<'EOF'
#!/usr/bin/env bash
cat "${VERIFY_TEST_NM_OUTPUT:?}"
EOF
chmod +x "${FAKE_BIN}/readelf" "${FAKE_BIN}/nm"
cat > "${READELF_OK}" <<'EOF'
Dynamic section at offset 0x1 contains 1 entry:
0x0000000000000001 (NEEDED) Shared library: [libwayland-client.so.0]
EOF
cat > "${NM_OK}" <<'EOF'
0000000000000001 T wl_proxy_destroy
0000000000000002 T wl_proxy_marshal_array_flags
0000000000000003 T wl_proxy_marshal_flags
0000000000000004 T wl_proxy_marshal
0000000000000005 T wl_proxy_marshal_array
0000000000000006 T wl_proxy_marshal_constructor
0000000000000007 T wl_proxy_marshal_constructor_versioned
0000000000000008 T wl_proxy_marshal_array_constructor
0000000000000009 T wl_proxy_marshal_array_constructor_versioned
EOF
run_link_verifier() {
PATH="${FAKE_BIN}:${PATH}" \
VERIFY_TEST_READELF_OUTPUT="$1" \
VERIFY_TEST_NM_OUTPUT="$2" \
bash "${STATIC_LINK_VERIFIER}" /bin/true
}
expect_link_verifier_failure() {
local description="$1" expected_message="$2" readelf_output="$3" nm_output="$4"
local output status
set +e
output="$(run_link_verifier "${readelf_output}" "${nm_output}" 2>&1)"
status=$?
set -e
if [[ "${status}" -eq 0 ]]; then
echo "Static linkage verifier unexpectedly accepted ${description}" >&2
exit 1
fi
grep -Fq "${expected_message}" <<< "${output}" || {
echo "Static linkage verifier rejected ${description} for an unrelated reason:" >&2
echo "${output}" >&2
exit 1
}
}
run_link_verifier "${READELF_OK}" "${NM_OK}" >/dev/null
READELF_DYNAMIC_LAYER="${WORK_DIR}/readelf-dynamic-layer.txt"
cp "${READELF_OK}" "${READELF_DYNAMIC_LAYER}"
printf '%s\n' ' 0x0000000000000001 (NEEDED) Shared library: [libgtk4-layer-shell.so.0]' \
>> "${READELF_DYNAMIC_LAYER}"
expect_link_verifier_failure "a dynamic layer-shell dependency" \
"still dynamically requires gtk4-layer-shell" \
"${READELF_DYNAMIC_LAYER}" "${NM_OK}"
READELF_NO_WAYLAND="${WORK_DIR}/readelf-no-wayland.txt"
grep -Fv 'libwayland-client.so.0' "${READELF_OK}" > "${READELF_NO_WAYLAND}"
expect_link_verifier_failure "a missing libwayland-client dependency" \
"does not retain libwayland-client.so.0 in DT_NEEDED" \
"${READELF_NO_WAYLAND}" "${NM_OK}"
for shim in \
wl_proxy_destroy \
wl_proxy_marshal_array_flags \
wl_proxy_marshal_flags \
wl_proxy_marshal \
wl_proxy_marshal_array \
wl_proxy_marshal_constructor \
wl_proxy_marshal_constructor_versioned \
wl_proxy_marshal_array_constructor \
wl_proxy_marshal_array_constructor_versioned; do
nm_without_shim="${WORK_DIR}/nm-without-${shim}.txt"
awk -v omitted="${shim}" '$3 != omitted' "${NM_OK}" > "${nm_without_shim}"
expect_link_verifier_failure "a binary missing ${shim}" \
"does not export required gtk4-layer-shell shim ${shim}" \
"${READELF_OK}" "${nm_without_shim}"
done
# All three release formats retain the notice for statically embedded code.
test -f "${REPO_ROOT}/packaging/licenses/gtk4-layer-shell.LICENSE"
assert_contains "${PACKAGE_CONFIG}" "dst: /usr/share/licenses/wayscriber/LICENSE.gtk4-layer-shell"
assert_contains "${PACKAGE_SCRIPT}" 'LICENSE.gtk4-layer-shell'
# Exercise the actual AUR updater against prebuilt metadata. The source AUR
# recipe intentionally keeps gtk4-layer-shell; wayscriber-bin must remove it.
AUR_BIN_DIR="${WORK_DIR}/wayscriber-bin"
MANIFEST="${WORK_DIR}/manifest.json"
mkdir -p "${AUR_BIN_DIR}"
cat > "${AUR_BIN_DIR}/PKGBUILD" <<'EOF'
pkgname=wayscriber-bin
pkgver=0.9.22
pkgrel=1
depends=(
'cairo'
'wayland'
'pango'
'gcc-libs'
'glibc'
'gtk4'
'gtk4-layer-shell'
'wl-clipboard'
)
source_x86_64=("old.tar.gz::https://example.invalid/old.tar.gz")
sha256sums_x86_64=('old')
_tarball="wayscriber-v${pkgver}-linux-${CARCH}.tar.gz"
package() {
local srcdir_tmp="${srcdir}/extract"
rm -rf "${srcdir_tmp}"
mkdir -p "${srcdir_tmp}"
tar -xzf "${srcdir}/${_tarball}" -C "${srcdir_tmp}" --strip-components=1
install -Dm755 "${srcdir_tmp}/usr/bin/wayscriber" "$pkgdir/usr/bin/wayscriber"
install -Dm644 "${srcdir_tmp}/usr/lib/systemd/user/wayscriber.service" "$pkgdir/usr/lib/systemd/user/wayscriber.service"
install -Dm644 "${srcdir_tmp}/usr/share/doc/wayscriber/config.example.toml" "$pkgdir/usr/share/doc/wayscriber/config.example.toml"
install -Dm644 "${srcdir_tmp}/usr/share/doc/wayscriber/README.md" "$pkgdir/usr/share/doc/wayscriber/README.md"
[ -f "${srcdir_tmp}/usr/share/doc/wayscriber/LICENSE" ] && install -Dm644 "${srcdir_tmp}/usr/share/doc/wayscriber/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE" || true
}
EOF
cat > "${AUR_BIN_DIR}/.SRCINFO" <<'EOF'
pkgbase = wayscriber-bin
pkgver = 0.9.22
pkgrel = 1
depends = gcc-libs
depends = gtk4-layer-shell
depends = wl-clipboard
source_x86_64 = old.tar.gz::https://example.invalid/old.tar.gz
sha256sums_x86_64 = old
pkgname = wayscriber-bin
EOF
cat > "${MANIFEST}" <<'EOF'
{
"version": "9.9.9",
"artifacts": [
{
"name": "wayscriber-v9.9.9-linux-x86_64.tar.gz",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"size": 1
}
]
}
EOF
git -C "${AUR_BIN_DIR}" init -q
git -C "${AUR_BIN_DIR}" add PKGBUILD .SRCINFO
bash "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
--version 9.9.9 \
--manifest "${MANIFEST}" \
--source-dir "${WORK_DIR}/missing-source" \
--bin-dir "${AUR_BIN_DIR}" \
--config-dir "${WORK_DIR}/missing-config" \
--no-configurator >/dev/null
assert_contains "${AUR_BIN_DIR}/PKGBUILD" "'gtk4'"
assert_contains "${AUR_BIN_DIR}/.SRCINFO" "depends = gtk4"
assert_not_contains "${AUR_BIN_DIR}/PKGBUILD" "'gtk4-layer-shell'"
assert_not_contains "${AUR_BIN_DIR}/.SRCINFO" "depends = gtk4-layer-shell"
assert_contains "${AUR_BIN_DIR}/PKGBUILD" 'install -Dm644 "${srcdir_tmp}/usr/share/licenses/wayscriber/LICENSE.gtk4-layer-shell" "$pkgdir/usr/share/licenses/$pkgname/LICENSE.gtk4-layer-shell"'
[[ "$(grep -Fc 'LICENSE.gtk4-layer-shell' "${AUR_BIN_DIR}/PKGBUILD")" -eq 1 ]] || {
echo "wayscriber-bin must install the gtk4-layer-shell notice exactly once" >&2
exit 1
}
assert_contains "${REPO_ROOT}/packaging/PKGBUILD" "'gtk4-layer-shell'"
assert_contains "${REPO_ROOT}/packaging/.SRCINFO" "depends = gtk4-layer-shell"
assert_contains "${REPO_ROOT}/packaging/PKGBUILD" "'libadwaita>=1.4'"
assert_contains "${REPO_ROOT}/packaging/.SRCINFO" "depends = libadwaita>=1.4"
# The configurator AUR channel is required by default. The hosted clone step
# must fail before the updater runs rather than silently publish two channels.
assert_not_contains "${RELEASE_WORKFLOW}" \
"git clone ssh://aur@aur.archlinux.org/wayscriber-configurator.git aur-wayscriber-configurator || true"
AUR_SOURCE_SHA='abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789'
AUR_FAKE_BIN="${WORK_DIR}/aur-fake-bin"
AUR_REAL_GIT="$(command -v git)"
mkdir -p "${AUR_FAKE_BIN}"
cat > "${AUR_FAKE_BIN}/curl" <<'EOF'
#!/usr/bin/env bash
output=""
while [[ $# -gt 0 ]]; do
if [[ "$1" == "-o" ]]; then
output="$2"
shift 2
else
shift
fi
done
case "${AUR_TEST_CURL_MODE:-fail}" in
empty)
: > "${output:?curl fixture did not receive -o}"
exit 0
;;
*)
echo 'AUR_TEST_NETWORK_ACCESS' >&2
exit 97
;;
esac
EOF
cat > "${AUR_FAKE_BIN}/git" <<'EOF'
#!/usr/bin/env bash
for arg in "$@"; do
if [[ "$arg" == "push" && -n "${AUR_TEST_GIT_LOG:-}" ]]; then
printf 'push %s\n' "$*" >> "${AUR_TEST_GIT_LOG}"
exit 96
fi
done
exec "${AUR_TEST_REAL_GIT:?}" "$@"
EOF
chmod +x "${AUR_FAKE_BIN}/curl" "${AUR_FAKE_BIN}/git"
commit_aur_fixture() {
local dir="$1"
git -C "${dir}" -c user.name='Wayscriber Test' \
-c user.email='wayscriber-test@example.invalid' \
commit -qm 'test fixture'
}
assert_clean_checkout_at_head() {
local dir="$1" expected_head="$2" context="$3"
[[ "$(git -C "${dir}" rev-parse HEAD)" == "${expected_head}" ]] || {
echo "${context}: checkout HEAD changed" >&2
exit 1
}
[[ -z "$(git -C "${dir}" status --porcelain)" ]] || {
echo "${context}: checkout worktree changed" >&2
git -C "${dir}" status --short >&2
exit 1
}
}
write_source_clone() {
local dir="$1" pkgver="$2" pkgrel="$3"
mkdir -p "${dir}"
cat > "${dir}/PKGBUILD" <<EOF
pkgname=wayscriber
pkgver=${pkgver}
pkgrel=${pkgrel}
install=wayscriber.install
depends=(
'gcc-libs'
'wl-clipboard'
)
makedepends=(
'git'
)
source=("old.tar.gz::https://example.invalid/old.tar.gz")
sha256sums=('old')
package() {
cd "\$pkgname"
install -Dm755 "target/release/wayscriber" "\$pkgdir/usr/bin/wayscriber"
}
EOF
cat > "${dir}/.SRCINFO" <<EOF
pkgbase = wayscriber
pkgver = ${pkgver}
pkgrel = ${pkgrel}
install = wayscriber.install
depends = gcc-libs
depends = wl-clipboard
makedepends = git
source = old.tar.gz::https://example.invalid/old.tar.gz
sha256sums = old
pkgname = wayscriber
EOF
touch "${dir}/wayscriber.install"
git -C "${dir}" init -q
git -C "${dir}" add PKGBUILD .SRCINFO wayscriber.install
commit_aur_fixture "${dir}"
}
write_configurator_clone() {
local dir="$1" pkgver="$2" pkgrel="$3"
mkdir -p "${dir}"
cat > "${dir}/PKGBUILD" <<EOF
pkgname=wayscriber-configurator
pkgver=${pkgver}
pkgrel=${pkgrel}
pkgdesc='GUI configurator for wayscriber (Iced)'
depends=(
'gcc-libs'
)
makedepends=(
'git'
)
source=("old.tar.gz::https://example.invalid/old.tar.gz")
sha256sums=('old')
build() {
cd wayscriber
}
package() {
cd wayscriber
install -Dm755 "target/release/wayscriber-configurator" "\$pkgdir/usr/bin/wayscriber-configurator"
install -Dm644 README.md "\$pkgdir/usr/share/doc/\$pkgname/README.md"
[ -f LICENSE ] && install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE" || true
}
EOF
cat > "${dir}/.SRCINFO" <<EOF
pkgbase = wayscriber-configurator
pkgdesc = GUI configurator for wayscriber (Iced)
pkgver = ${pkgver}
pkgrel = ${pkgrel}
depends = gcc-libs
makedepends = git
source = old.tar.gz::https://example.invalid/old.tar.gz
sha256sums = old
pkgname = wayscriber-configurator
EOF
git -C "${dir}" init -q
git -C "${dir}" add PKGBUILD .SRCINFO
commit_aur_fixture "${dir}"
}
run_aur_updater() {
local cwd="$1"
shift
local version_args=()
if [[ "${AUR_TEST_USE_MANIFEST_VERSION:-0}" -eq 0 ]]; then
version_args=(--version 9.9.9)
fi
(
cd "${cwd}"
AUR_TEST_CURL_MODE="${AUR_TEST_CURL_MODE:-fail}" \
AUR_TEST_GIT_LOG="${AUR_TEST_GIT_LOG:-}" \
AUR_TEST_REAL_GIT="${AUR_REAL_GIT}" \
PATH="${AUR_FAKE_BIN}:${PATH}" \
bash "${REPO_ROOT}/tools/update-aur-from-manifest.sh" \
"${version_args[@]}" \
--manifest "${MANIFEST}" \
"$@"
)
}
expect_aur_failure() {
local expected="$1" cwd="$2"
shift 2
local output="${WORK_DIR}/aur-failure-output"
if run_aur_updater "${cwd}" "$@" >"${output}" 2>&1; then
echo "Expected AUR updater failure containing: ${expected}" >&2
exit 1
fi
assert_contains "${output}" "${expected}"
}
expect_invalid_aur_version() {
local expected="$1" manifest="$2"
shift 2
local push_log="${WORK_DIR}/aur-invalid-version-push-log"
AUR_TEST_USE_MANIFEST_VERSION=1 \
AUR_TEST_GIT_LOG="${push_log}" \
expect_aur_failure "${expected}" "${WORK_DIR}" \
--manifest "${manifest}" \
--source-dir aur-invalid-version-source \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator \
--source-sha256 "${AUR_SOURCE_SHA}" \
--push \
"$@"
assert_clean_checkout_at_head \
"${AUR_INVALID_VERSION_SOURCE}" \
"${AUR_INVALID_VERSION_HEAD}" \
"invalid release version"
[[ ! -s "${push_log}" ]] || {
echo "AUR updater pushed for an invalid release version" >&2
exit 1
}
}
# Release identity is validated before checkout discovery or mutation. A JSON
# string is required when the manifest supplies it, and both input routes use
# the same MAJOR.MINOR.PATCH[.HOTFIX] grammar as the rest of the release tools.
AUR_INVALID_VERSION_SOURCE="${WORK_DIR}/aur-invalid-version-source"
AUR_VERSION_MISSING_MANIFEST="${WORK_DIR}/manifest-version-missing.json"
AUR_VERSION_NULL_MANIFEST="${WORK_DIR}/manifest-version-null.json"
AUR_VERSION_ARRAY_MANIFEST="${WORK_DIR}/manifest-version-array.json"
AUR_VERSION_OBJECT_MANIFEST="${WORK_DIR}/manifest-version-object.json"
AUR_VERSION_MALFORMED_MANIFEST="${WORK_DIR}/manifest-version-malformed.json"
write_source_clone "${AUR_INVALID_VERSION_SOURCE}" 9.9.8 2
AUR_INVALID_VERSION_HEAD="$(git -C "${AUR_INVALID_VERSION_SOURCE}" rev-parse HEAD)"
cat > "${AUR_VERSION_MISSING_MANIFEST}" <<'EOF'
{"artifacts":[]}
EOF
cat > "${AUR_VERSION_NULL_MANIFEST}" <<'EOF'
{"version":null,"artifacts":[]}
EOF
cat > "${AUR_VERSION_ARRAY_MANIFEST}" <<'EOF'
{"version":[9,9,9],"artifacts":[]}
EOF
cat > "${AUR_VERSION_OBJECT_MANIFEST}" <<'EOF'
{"version":{"major":9,"minor":9,"patch":9},"artifacts":[]}
EOF
cat > "${AUR_VERSION_MALFORMED_MANIFEST}" <<'EOF'
{"version":"9.9","artifacts":[]}
EOF
expect_invalid_aur_version "Manifest version must be a string" \
"${AUR_VERSION_MISSING_MANIFEST}"
expect_invalid_aur_version "Manifest version must be a string" \
"${AUR_VERSION_NULL_MANIFEST}"
expect_invalid_aur_version "Manifest version must be a string" \
"${AUR_VERSION_ARRAY_MANIFEST}"
expect_invalid_aur_version "Manifest version must be a string" \
"${AUR_VERSION_OBJECT_MANIFEST}"
expect_invalid_aur_version "Invalid manifest version '9.9'" \
"${AUR_VERSION_MALFORMED_MANIFEST}"
expect_invalid_aur_version "Invalid --version 'release-9.9.9'" \
"${MANIFEST}" \
--version release-9.9.9
# Relative clone paths used to be resolved after pushd, turning `dir/PKGBUILD`
# into `dir/dir/PKGBUILD` and resetting same-version hotfixes to pkgrel=1.
AUR_SOURCE_HOTFIX="${WORK_DIR}/aur-source-hotfix"
write_source_clone "${AUR_SOURCE_HOTFIX}" 9.9.9 3
run_aur_updater "${WORK_DIR}" \
--source-dir aur-source-hotfix \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator \
--source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
assert_contains "${AUR_SOURCE_HOTFIX}/PKGBUILD" "pkgrel=4"
assert_contains "${AUR_SOURCE_HOTFIX}/.SRCINFO" "pkgrel = 4"
AUR_CONFIG_HOTFIX="${WORK_DIR}/aur-config-hotfix"
write_configurator_clone "${AUR_CONFIG_HOTFIX}" 9.9.9 3
run_aur_updater "${WORK_DIR}" \
--source-dir missing-source \
--bin-dir missing-bin \
--config-dir aur-config-hotfix \
--source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" "pkgrel=4"
assert_contains "${AUR_CONFIG_HOTFIX}/.SRCINFO" "pkgrel = 4"
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" \
"pkgdesc='GUI configurator for wayscriber (GTK4/libadwaita)'"
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" "'gtk4'"
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" "'libadwaita>=1.4'"
assert_not_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" "(Iced)"
assert_contains "${AUR_CONFIG_HOTFIX}/.SRCINFO" \
"pkgdesc = GUI configurator for wayscriber (GTK4/libadwaita)"
assert_contains "${AUR_CONFIG_HOTFIX}/.SRCINFO" "depends = gtk4"
assert_contains "${AUR_CONFIG_HOTFIX}/.SRCINFO" "depends = libadwaita>=1.4"
assert_not_contains "${AUR_CONFIG_HOTFIX}/.SRCINFO" "(Iced)"
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" \
'packaging/wayscriber-configurator.desktop'
for size in 16 19 22 24 38 64 128; do
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" \
"packaging/icons/wayscriber-configurator-${size}.png"
done
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" \
'packaging/icons/wayscriber-configurator.svg'
# A missing required configurator clone aborts before an earlier source channel
# is touched. Skipping it remains available, but only as an explicit decision.
AUR_PREFLIGHT_SOURCE="${WORK_DIR}/aur-preflight-source"
write_source_clone "${AUR_PREFLIGHT_SOURCE}" 9.9.9 3
cp "${AUR_PREFLIGHT_SOURCE}/PKGBUILD" "${WORK_DIR}/aur-preflight-source.before"
expect_aur_failure "wayscriber-configurator AUR clone not found" "${WORK_DIR}" \
--source-dir aur-preflight-source \
--bin-dir missing-bin \
--config-dir missing-config \
--source-sha256 "${AUR_SOURCE_SHA}"
cmp "${WORK_DIR}/aur-preflight-source.before" "${AUR_PREFLIGHT_SOURCE}/PKGBUILD"
AUR_NOT_GIT="${WORK_DIR}/aur-not-git"
mkdir -p "${AUR_NOT_GIT}"
touch "${AUR_NOT_GIT}/PKGBUILD" "${AUR_NOT_GIT}/.SRCINFO"
expect_aur_failure "wayscriber AUR path is not a Git worktree" "${WORK_DIR}" \
--source-dir aur-not-git \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator \
--source-sha256 "${AUR_SOURCE_SHA}"
AUR_SKIP_OUTPUT="${WORK_DIR}/aur-skip-output"
run_aur_updater "${WORK_DIR}" \
--source-dir missing-source \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator >"${AUR_SKIP_OUTPUT}" 2>&1
assert_contains "${AUR_SKIP_OUTPUT}" "--no-configurator was passed"
# Download and checksum validation also run before mutation. The fake curl
# makes accidental network use deterministic and proves the clone stays intact.
AUR_CHECKSUM_SOURCE="${WORK_DIR}/aur-checksum-source"
write_source_clone "${AUR_CHECKSUM_SOURCE}" 9.9.8 2
cp "${AUR_CHECKSUM_SOURCE}/PKGBUILD" "${WORK_DIR}/aur-checksum-source.before"
expect_aur_failure "Failed to download the source archive" "${WORK_DIR}" \
--source-dir aur-checksum-source \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator
cmp "${WORK_DIR}/aur-checksum-source.before" "${AUR_CHECKSUM_SOURCE}/PKGBUILD"
AUR_TEST_CURL_MODE=empty expect_aur_failure \
"Downloaded an empty source archive" "${WORK_DIR}" \
--source-dir aur-checksum-source \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator
cmp "${WORK_DIR}/aur-checksum-source.before" "${AUR_CHECKSUM_SOURCE}/PKGBUILD"
expect_aur_failure "Source archive checksum is not a sha256 digest" "${WORK_DIR}" \
--source-dir aur-checksum-source \
--bin-dir missing-bin \
--config-dir missing-config \
--no-configurator \
--source-sha256 invalid
# Binary artifact lookup is a cardinality-and-shape contract, not merely a
# nonempty string check. All failures happen before the bin checkout mutates.
AUR_BIN_MISSING_MANIFEST="${WORK_DIR}/manifest-bin-missing.json"
AUR_BIN_DUPLICATE_MANIFEST="${WORK_DIR}/manifest-bin-duplicate.json"
AUR_BIN_DUPLICATE_NULL_MANIFEST="${WORK_DIR}/manifest-bin-duplicate-null.json"
AUR_BIN_MALFORMED_MANIFEST="${WORK_DIR}/manifest-bin-malformed.json"
cp "${AUR_BIN_DIR}/PKGBUILD" "${WORK_DIR}/aur-bin-checksum.before"
cat > "${AUR_BIN_MISSING_MANIFEST}" <<'EOF'
{"version":"9.9.9","artifacts":[]}
EOF
cat > "${AUR_BIN_DUPLICATE_MANIFEST}" <<EOF
{"version":"9.9.9","artifacts":[
{"name":"wayscriber-v9.9.9-linux-x86_64.tar.gz","sha256":"${AUR_SOURCE_SHA}"},
{"name":"wayscriber-v9.9.9-linux-x86_64.tar.gz","sha256":"${AUR_SOURCE_SHA}"}
]}
EOF
cat > "${AUR_BIN_DUPLICATE_NULL_MANIFEST}" <<EOF
{"version":"9.9.9","artifacts":[
{"name":"wayscriber-v9.9.9-linux-x86_64.tar.gz","sha256":"${AUR_SOURCE_SHA}"},
{"name":"wayscriber-v9.9.9-linux-x86_64.tar.gz","sha256":null}
]}
EOF
cat > "${AUR_BIN_MALFORMED_MANIFEST}" <<'EOF'
{"version":"9.9.9","artifacts":[
{"name":"wayscriber-v9.9.9-linux-x86_64.tar.gz","sha256":"not-a-digest"}
]}
EOF
expect_aur_failure "found 0" "${WORK_DIR}" \
--manifest "${AUR_BIN_MISSING_MANIFEST}" \
--source-dir missing-source \
--bin-dir "${AUR_BIN_DIR}" \
--config-dir missing-config \
--no-configurator
expect_aur_failure "found 2" "${WORK_DIR}" \
--manifest "${AUR_BIN_DUPLICATE_MANIFEST}" \
--source-dir missing-source \
--bin-dir "${AUR_BIN_DIR}" \
--config-dir missing-config \
--no-configurator
expect_aur_failure "found 2" "${WORK_DIR}" \
--manifest "${AUR_BIN_DUPLICATE_NULL_MANIFEST}" \
--source-dir missing-source \
--bin-dir "${AUR_BIN_DIR}" \
--config-dir missing-config \
--no-configurator
expect_aur_failure "not a 64-character hexadecimal digest" "${WORK_DIR}" \
--manifest "${AUR_BIN_MALFORMED_MANIFEST}" \
--source-dir missing-source \
--bin-dir "${AUR_BIN_DIR}" \
--config-dir missing-config \
--no-configurator
cmp "${WORK_DIR}/aur-bin-checksum.before" "${AUR_BIN_DIR}/PKGBUILD"
# A deterministic failure in the last selected checkout must happen before
# any earlier checkout is committed or pushed. This configurator fixture lacks
# the dependency anchor needed by its GTK migration.
AUR_LATE_SOURCE="${WORK_DIR}/aur-late-source"
AUR_LATE_CONFIG="${WORK_DIR}/aur-late-config"
AUR_PUSH_LOG="${WORK_DIR}/aur-push-log"
write_source_clone "${AUR_LATE_SOURCE}" 9.9.8 1
write_configurator_clone "${AUR_LATE_CONFIG}" 9.9.8 1
AUR_LATE_SOURCE_HEAD="$(git -C "${AUR_LATE_SOURCE}" rev-parse HEAD)"
sed -i "/'gcc-libs'/d" "${AUR_LATE_CONFIG}/PKGBUILD"
sed -i '/depends = gcc-libs/d' "${AUR_LATE_CONFIG}/.SRCINFO"
AUR_TEST_GIT_LOG="${AUR_PUSH_LOG}" expect_aur_failure \
"missing dependency anchor 'gcc-libs'" "${WORK_DIR}" \
--source-dir aur-late-source \
--bin-dir missing-bin \
--config-dir aur-late-config \
--source-sha256 "${AUR_SOURCE_SHA}" \
--push
assert_clean_checkout_at_head \
"${AUR_LATE_SOURCE}" \
"${AUR_LATE_SOURCE_HEAD}" \
"late configurator preflight failure"
[[ ! -s "${AUR_PUSH_LOG}" ]] || {
echo "AUR updater pushed before every selected checkout was prepared" >&2
exit 1
}
# Reapply to prove idempotence, then execute both generated package() bodies.
# Compare installed assets with the canonical package manifest, rather than
# only matching the updater's own generated text.
run_aur_updater "${WORK_DIR}" \
--source-dir "${AUR_SOURCE_HOTFIX}" \
--bin-dir "${AUR_BIN_DIR}" \
--no-configurator \
--source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
for recipe in "${AUR_SOURCE_HOTFIX}" "${AUR_BIN_DIR}"; do
[[ "$(grep -Fxc '# Wayscriber desktop integration' "$recipe/PKGBUILD")" == 1 ]] || {
echo "Expected exactly one managed desktop block in $recipe/PKGBUILD" >&2
exit 1
}
done
# A managed block is exact state, so assets removed from the manifest must not
# survive merely because every current line is also present.
STALE_AUR_ASSET_LINE=' install -Dm644 packaging/icons/retired-icon.png "$pkgdir/usr/share/icons/hicolor/obsolete/retired-icon.png"'
STALE_CONFIGURATOR_ASSET_LINE=' install -Dm644 packaging/icons/retired-legacy-icon.png "$pkgdir/usr/share/icons/hicolor/obsolete/retired-legacy-icon.png"'
STALE_AUR_ASSET_LINE="${STALE_AUR_ASSET_LINE}" perl -0pi -e '
s{^# End Wayscriber desktop integration$}{$ENV{STALE_AUR_ASSET_LINE} . "\n" . $&}me
' "${AUR_SOURCE_HOTFIX}/PKGBUILD"
STALE_CONFIGURATOR_ASSET_LINE="${STALE_CONFIGURATOR_ASSET_LINE}" perl -0pi -e '
s{^# End Wayscriber configurator desktop integration$}{$ENV{STALE_CONFIGURATOR_ASSET_LINE} . "\n" . $&}me
' "${AUR_CONFIG_HOTFIX}/PKGBUILD"
run_aur_updater "${WORK_DIR}" \
--source-dir "${AUR_SOURCE_HOTFIX}" --bin-dir missing-bin \
--config-dir "${AUR_CONFIG_HOTFIX}" --source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
assert_not_contains "${AUR_SOURCE_HOTFIX}/PKGBUILD" "${STALE_AUR_ASSET_LINE}"
assert_not_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" "${STALE_CONFIGURATOR_ASSET_LINE}"
# Pre-marker recipes can contain an older manifest subset and retain removed
# assets. Migrate each legacy set to an exact managed block.
AUR_MARKERLESS_SOURCE="${WORK_DIR}/aur-markerless-source"
AUR_MARKERLESS_BIN="${WORK_DIR}/aur-markerless-bin"
AUR_MARKERLESS_CONFIG="${WORK_DIR}/aur-markerless-config"
cp -a "${AUR_SOURCE_HOTFIX}" "${AUR_MARKERLESS_SOURCE}"
cp -a "${AUR_BIN_DIR}" "${AUR_MARKERLESS_BIN}"
cp -a "${AUR_CONFIG_HOTFIX}" "${AUR_MARKERLESS_CONFIG}"
for recipe in "${AUR_MARKERLESS_SOURCE}" "${AUR_MARKERLESS_BIN}"; do
sed -i '/^# \(End \)\?Wayscriber desktop integration$/d' "${recipe}/PKGBUILD"
sed -i '/wayscriber-symbolic\.svg/d' "${recipe}/PKGBUILD"
printf '%s\n' "${STALE_AUR_ASSET_LINE}" >> "${recipe}/PKGBUILD"
done
sed -i '/^# \(End \)\?Wayscriber configurator desktop integration$/d' "${AUR_MARKERLESS_CONFIG}/PKGBUILD"
sed -i '/wayscriber-configurator\.svg/d' "${AUR_MARKERLESS_CONFIG}/PKGBUILD"
printf '%s\n' "${STALE_CONFIGURATOR_ASSET_LINE}" >> "${AUR_MARKERLESS_CONFIG}/PKGBUILD"
run_aur_updater "${WORK_DIR}" \
--source-dir "${AUR_MARKERLESS_SOURCE}" --bin-dir "${AUR_MARKERLESS_BIN}" \
--config-dir "${AUR_MARKERLESS_CONFIG}" --source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
for recipe in "${AUR_MARKERLESS_SOURCE}" "${AUR_MARKERLESS_BIN}"; do
[[ "$(grep -Fxc '# Wayscriber desktop integration' "${recipe}/PKGBUILD")" == 1 ]] || {
echo "Markerless Wayscriber desktop assets were not normalized in ${recipe}" >&2
exit 1
}
assert_not_contains "${recipe}/PKGBUILD" "${STALE_AUR_ASSET_LINE}"
done
[[ "$(grep -Fxc '# Wayscriber configurator desktop integration' "${AUR_MARKERLESS_CONFIG}/PKGBUILD")" == 1 ]] || {
echo "Markerless configurator desktop assets were not normalized" >&2
exit 1
}
assert_not_contains "${AUR_MARKERLESS_CONFIG}/PKGBUILD" "${STALE_CONFIGURATOR_ASSET_LINE}"
AUR_ASSET_SOURCE="${WORK_DIR}/aur-asset-source"
AUR_ASSET_STAGE="${WORK_DIR}/aur-asset-stage"
mkdir -p "${AUR_ASSET_SOURCE}/wayscriber-9.9.9/target/release"
cp -a "${REPO_ROOT}/packaging" "${AUR_ASSET_SOURCE}/wayscriber-9.9.9/"
printf '#!/usr/bin/env bash\nexit 0\n' > "${AUR_ASSET_SOURCE}/wayscriber-9.9.9/target/release/wayscriber"
srcdir="${AUR_ASSET_SOURCE}" pkgdir="${AUR_ASSET_STAGE}" \
bash -c 'source "$1"; cd "$srcdir"; package' -- "${AUR_SOURCE_HOTFIX}/PKGBUILD"
# Complete the mock release tarball's non-desktop payload for the binary recipe.
mkdir -p "${AUR_ASSET_STAGE}/usr/lib/systemd/user" \
"${AUR_ASSET_STAGE}/usr/share/doc/wayscriber" \
"${AUR_ASSET_STAGE}/usr/share/licenses/wayscriber"
cp "${REPO_ROOT}/packaging/wayscriber.service" "${AUR_ASSET_STAGE}/usr/lib/systemd/user/"
cp "${REPO_ROOT}/config.example.toml" "${REPO_ROOT}/README.md" "${REPO_ROOT}/LICENSE" \
"${AUR_ASSET_STAGE}/usr/share/doc/wayscriber/"
cp "${REPO_ROOT}/packaging/licenses/gtk4-layer-shell.LICENSE" \
"${AUR_ASSET_STAGE}/usr/share/licenses/wayscriber/LICENSE.gtk4-layer-shell"
tar -czf "${AUR_ASSET_SOURCE}/wayscriber-v9.9.9-linux-x86_64.tar.gz" \
-C "${WORK_DIR}" aur-asset-stage
AUR_BIN_STAGE="${WORK_DIR}/aur-bin-stage"
srcdir="${AUR_ASSET_SOURCE}" pkgdir="${AUR_BIN_STAGE}" CARCH=x86_64 \
bash -c 'source "$1"; cd "$srcdir"; package' -- "${AUR_BIN_DIR}/PKGBUILD"
while read -r asset; do
for stage in "${AUR_ASSET_STAGE}" "${AUR_BIN_STAGE}"; do
[[ -f "$stage$asset" && "$(stat -c '%a' "$stage$asset")" == 644 ]] || {
echo "Expected installed desktop asset with mode 644: $stage$asset" >&2
exit 1
}
done
cmp "${AUR_ASSET_STAGE}${asset}" "${AUR_BIN_STAGE}${asset}" || {
echo "Source and binary package assets differ: $asset" >&2
exit 1
}
done < <(awk '/^ dst: \/usr\/share\/(applications|icons|pixmaps)\// { print $2 }' "${PACKAGE_CONFIG}")
[[ ! -e "${AUR_BIN_STAGE}/usr/bin/wayscriber-configurator" ]] || {
echo 'The binary package unexpectedly includes the configurator' >&2
exit 1
}
# Repair missing assets in both current and legacy managed blocks.
SOURCE_CONFIGURATOR_BLOCK_BEFORE="$(awk '
/^# Wayscriber configurator desktop integration$/ { inside = 1 }
inside { print }
/^# End Wayscriber configurator desktop integration$/ { exit }
' "${AUR_SOURCE_HOTFIX}/PKGBUILD")"
sed -i '/^# End Wayscriber configurator desktop integration$/d; /install .*scalable\/apps\/wayscriber-configurator.svg/d' "${AUR_CONFIG_HOTFIX}/PKGBUILD"
sed -i '/install .*symbolic\/apps\/wayscriber-symbolic.svg/d' "${AUR_BIN_DIR}/PKGBUILD"
sed -i '/^# End Wayscriber desktop integration$/d; /install .*symbolic\/apps\/wayscriber-symbolic.svg/d' "${AUR_SOURCE_HOTFIX}/PKGBUILD"
run_aur_updater "${WORK_DIR}" \
--source-dir "${AUR_SOURCE_HOTFIX}" --bin-dir "${AUR_BIN_DIR}" \
--config-dir "${AUR_CONFIG_HOTFIX}" --source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
for recipe in "${AUR_SOURCE_HOTFIX}" "${AUR_BIN_DIR}"; do
assert_contains "$recipe/PKGBUILD" 'symbolic/apps/wayscriber-symbolic.svg'
assert_contains "$recipe/PKGBUILD" '# End Wayscriber desktop integration'
done
SOURCE_CONFIGURATOR_BLOCK_AFTER="$(awk '
/^# Wayscriber configurator desktop integration$/ { inside = 1 }
inside { print }
/^# End Wayscriber configurator desktop integration$/ { exit }
' "${AUR_SOURCE_HOTFIX}/PKGBUILD")"
[[ "${SOURCE_CONFIGURATOR_BLOCK_AFTER}" == "${SOURCE_CONFIGURATOR_BLOCK_BEFORE}" ]] || {
echo 'Repairing the source desktop block changed configurator assets' >&2
exit 1
}
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" 'scalable/apps/wayscriber-configurator.svg'
assert_contains "${AUR_CONFIG_HOTFIX}/PKGBUILD" '# End Wayscriber configurator desktop integration'
# The interactive updater installs the canonical template, which already has
# desktop assets. The manifest updater must accept that route without duplicates.
AUR_TEMPLATE_SOURCE="${WORK_DIR}/aur-template-source"
mkdir -p "${AUR_TEMPLATE_SOURCE}"
cp "${REPO_ROOT}/packaging/PKGBUILD" "${REPO_ROOT}/packaging/.SRCINFO" "${AUR_TEMPLATE_SOURCE}/"
git -C "${AUR_TEMPLATE_SOURCE}" init -q
run_aur_updater "${WORK_DIR}" \
--source-dir "${AUR_TEMPLATE_SOURCE}" --bin-dir missing-bin \
--no-configurator --source-sha256 "${AUR_SOURCE_SHA}" >/dev/null
[[ "$(grep -Fc 'packaging/wayscriber.desktop' "${AUR_TEMPLATE_SOURCE}/PKGBUILD")" == 1 ]] || {
echo 'Canonical template received duplicate desktop integration' >&2
exit 1
}
# A version bump must retain a locked registry dependency even when a newer
# compatible release exists. A local directory source makes this fully offline.
BUMP_REPO="${WORK_DIR}/bump-repo"
BUMP_FAKE_BIN="${WORK_DIR}/bump-fake-bin"
mkdir -p "${BUMP_REPO}/tools" "${BUMP_REPO}/src" \
"${BUMP_REPO}/configurator/src" "${BUMP_REPO}/packaging" \
"${BUMP_REPO}/.cargo" "${BUMP_FAKE_BIN}"
cp "${REPO_ROOT}/tools/bump-version.sh" "${BUMP_REPO}/tools/"
# This test isolates the bump's Cargo operation; real metadata consistency is
# checked separately by the canonical gate on this repository.
printf '#!/usr/bin/env bash\nexit 0\n' > "${BUMP_REPO}/tools/check-version-consistency.sh"
printf '#!/usr/bin/env bash\nprintf "pkgbase = wayscriber\\n"\n' > "${BUMP_FAKE_BIN}/makepkg"
chmod +x "${BUMP_FAKE_BIN}/makepkg"
cat > "${BUMP_REPO}/Cargo.toml" <<'EOF'
[package]
name = "wayscriber"
version = "1.0.0"
edition = "2024"
[workspace]
members = ["configurator"]
[dependencies]
release-fixture = "1"
EOF
cat > "${BUMP_REPO}/configurator/Cargo.toml" <<'EOF'
[package]
name = "wayscriber-configurator"
version = "1.0.0"
edition = "2024"
[dependencies]
wayscriber = { path = ".." }
EOF
touch "${BUMP_REPO}/src/lib.rs" "${BUMP_REPO}/configurator/src/lib.rs"
printf "pkgver=1.0.0\nsha256sums=('SKIP')\n" > "${BUMP_REPO}/packaging/PKGBUILD"
cat > "${BUMP_REPO}/.cargo/config.toml" <<EOF
[source.crates-io]
replace-with = "fixture"
[source.fixture]
directory = "${BUMP_REPO}/vendor"
EOF
for fixture_version in 1.0.0 1.1.0; do
fixture_dir="${BUMP_REPO}/vendor/release-fixture-${fixture_version}"
mkdir -p "${fixture_dir}/src"
printf '[package]\nname = "release-fixture"\nversion = "%s"\nedition = "2024"\n' \
"${fixture_version}" > "${fixture_dir}/Cargo.toml"
touch "${fixture_dir}/src/lib.rs"
printf '{"files":{},"package":"%s"}\n' "${AUR_SOURCE_SHA}" > "${fixture_dir}/.cargo-checksum.json"
if [[ "$fixture_version" == 1.0.0 ]]; then
(cd "${BUMP_REPO}" && cargo generate-lockfile --offline)
fi
done
BUMP_FAIL_BIN="${WORK_DIR}/bump-fail-bin"
BUMP_DRY_RUN_OUTPUT="${WORK_DIR}/bump-dry-run-output"
mkdir -p "${BUMP_FAIL_BIN}"
cat > "${BUMP_FAIL_BIN}/cargo" <<'EOF'
#!/usr/bin/env bash
if [[ "$*" == "update --workspace --offline --dry-run" ]]; then
exit 1
fi
exec "${BUMP_REAL_CARGO:?}" "$@"
EOF
chmod +x "${BUMP_FAIL_BIN}/cargo"
if BUMP_REAL_CARGO="$(command -v cargo)" PATH="${BUMP_FAIL_BIN}:${BUMP_FAKE_BIN}:${PATH}" \
bash "${BUMP_REPO}/tools/bump-version.sh" --dry-run 1.0.1 >"${BUMP_DRY_RUN_OUTPUT}" 2>&1; then
echo 'Expected the version-bump dry run to fail when offline resolution fails' >&2
exit 1
fi
assert_contains "${BUMP_DRY_RUN_OUTPUT}" 'cannot resolve locked dependencies offline'
cp "${BUMP_REPO}/Cargo.lock" "${WORK_DIR}/before-bump.lock"
for version in 1.0.1 1.0.1.1; do
PATH="${BUMP_FAKE_BIN}:${PATH}" bash "${BUMP_REPO}/tools/bump-version.sh" "$version" >/dev/null
# Both workspace packages change, but all remaining bytes must stay put.
sed 's/version = "1.0.1"/version = "1.0.0"/g' "${BUMP_REPO}/Cargo.lock" \
> "${WORK_DIR}/normalized-bump.lock"
cmp "${WORK_DIR}/before-bump.lock" "${WORK_DIR}/normalized-bump.lock"
done
# Prove the fixture exposes the original bug: resolving afresh selects 1.1.0.
(cd "${BUMP_REPO}" && cargo generate-lockfile --offline)
assert_contains "${BUMP_REPO}/Cargo.lock" 'version = "1.1.0"'
echo "Release packaging contract checks passed."