diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml new file mode 100644 index 000000000..4df4328ea --- /dev/null +++ b/.github/workflows/aur-publish.yml @@ -0,0 +1,302 @@ +# Publishes the official Robrix AUR package. +# +# Deliberately not a job in release.yml. That workflow creates every release as a +# draft, and a draft's asset URLs 404 for everyone but the token holder, so a +# PKGBUILD pushed from there would point at nothing. This fires the moment a human +# publishes the draft, which is when those URLs start resolving. +# +# The PKGBUILD comes from packaging/arch/PKGBUILD.in via +# packaging/aur/render-pkgbuild.sh, which probes the release for the arch payloads +# it actually has, so aarch64 turns itself on the first release that ships one. +# +# The AUR package must already exist. This workflow only updates it. + +name: Publish AUR package + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Published release tag to push to the AUR (e.g. v1.0.0-alpha.2)' + required: true + type: string + +# One push at a time, and never cancel one midway: a half-finished push to the AUR +# is not something we want to reason about. GitHub also keeps at most one run +# queued per group, so a superseded queued release needs a manual re-dispatch. +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + # No secrets here on purpose. This is the job that runs repo-controlled shell and + # a container, so the AUR key must not exist yet while it does. + render: + name: Render and test-build + runs-on: ubuntu-latest + timeout-minutes: 45 + outputs: + tag: ${{ steps.tag.outputs.tag }} + pkgname: ${{ steps.render.outputs.pkgname }} + pkgver: ${{ steps.render.outputs.pkgver }} + pkgrel: ${{ steps.render.outputs.pkgrel }} + changed: ${{ steps.compare.outputs.changed }} + steps: + # ref: main because the template is release tooling, not versioned content. + # Pinning it to the tag would mean re-tagging to fix a broken template. + - uses: actions/checkout@v5 + with: + ref: main + + - name: Resolve tag + id: tag + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + INPUT_TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + tag="${RELEASE_TAG:-${INPUT_TAG:-}}" + if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then + echo "::error::Refusing to publish '$tag'. Expected a vMAJOR.MINOR.PATCH[-prerelease] tag." + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + # Read-only over https, so no key is needed to find out where the AUR stands. + - name: Read the current AUR package + id: current + run: | + set -euo pipefail + pkgname="$(packaging/aur/render-pkgbuild.sh --print-pkgname)" + echo "pkgname=$pkgname" >> "$GITHUB_OUTPUT" + git clone --depth 1 "https://aur.archlinux.org/${pkgname}.git" aur + + ## An AUR clone succeeds even for a pkgbase that doesn't exist, so an empty + ## repo means a typo or that nobody has done the first push yet. + if [[ ! -f aur/PKGBUILD ]]; then + echo "::error::https://aur.archlinux.org/${pkgname}.git has no PKGBUILD. This workflow only updates an existing package; create it by cloning ssh://aur@aur.archlinux.org/${pkgname}.git and pushing a rendered PKGBUILD, .SRCINFO and LICENSE by hand." + exit 1 + fi + echo "old_pkgver=$(sed -n 's/^pkgver=//p' aur/PKGBUILD | head -n1)" >> "$GITHUB_OUTPUT" + echo "old_pkgrel=$(sed -n 's/^pkgrel=//p' aur/PKGBUILD | head -n1)" >> "$GITHUB_OUTPUT" + + # pkgrel resets to 1 on a new pkgver and only climbs for a packaging-only fix + # at the same pkgver, which is the one case where users would otherwise see + # no update at all. + - name: Render PKGBUILD + id: render + env: + TAG: ${{ steps.tag.outputs.tag }} + OLD_PKGVER: ${{ steps.current.outputs.old_pkgver }} + OLD_PKGREL: ${{ steps.current.outputs.old_pkgrel }} + run: | + set -euo pipefail + mkdir -p work + + old_pkgrel="$OLD_PKGREL" + [[ "$old_pkgrel" =~ ^[1-9][0-9]*$ ]] || old_pkgrel=1 + new_pkgver="$(packaging/aur/render-pkgbuild.sh --print-pkgver --tag "$TAG")" + + if [[ "$new_pkgver" == "$OLD_PKGVER" ]]; then + pkgrel="$old_pkgrel" + else + pkgrel=1 + fi + + render() { + packaging/aur/render-pkgbuild.sh --mode aur \ + --tag "$TAG" --repo "$GITHUB_REPOSITORY" --pkgrel "$1" --out work + } + render "$pkgrel" + + ## Same app version but different packaging, so bump pkgrel or nobody sees + ## it. The payloads are already in work/, so this re-render is text only. + if [[ "$new_pkgver" == "$OLD_PKGVER" ]] && ! cmp -s work/PKGBUILD aur/PKGBUILD; then + pkgrel=$(( old_pkgrel + 1 )) + render "$pkgrel" + fi + + ## The AUR wants a package-source license in the repo, separate from + ## Robrix's own. Arch's 0BSD text, verbatim. + cp packaging/aur/aur-repo-LICENSE work/LICENSE + + echo "pkgname=$(packaging/aur/render-pkgbuild.sh --print-pkgname)" >> "$GITHUB_OUTPUT" + echo "pkgver=$new_pkgver" >> "$GITHUB_OUTPUT" + echo "pkgrel=$pkgrel" >> "$GITHUB_OUTPUT" + + # .SRCINFO must come from makepkg, never by hand: the AUR serves its version + # metadata from that file, and a stale one is the classic AUR maintenance bug. + - name: Generate .SRCINFO and test-build + env: + OLD_PKGVER: ${{ steps.current.outputs.old_pkgver }} + run: | + set -euo pipefail + docker run --rm -v "$PWD/work:/work" -w /work \ + -e OLD_PKGVER -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \ + archlinux:base-devel bash -euo pipefail -c ' + ## These images ship without the pacman lsign key. + pacman-key --init && pacman-key --populate + ## Pin PKGEXT so the *.pkg.tar.zst globs below stay right. + printf "\nPKGEXT=.pkg.tar.zst\n" >> /etc/makepkg.conf + pacman -Syu --noconfirm --needed namcap || echo "::warning::namcap unavailable, skipping lint" + useradd --create-home builder + chown -R builder /work + + ## One-way ratchet, so an old tag dispatched by mistake cannot downgrade + ## the package for everyone who has it installed. + new_pkgver="$(sed -n "s/^pkgver=//p" PKGBUILD | head -n1)" + if [[ -n "${OLD_PKGVER:-}" ]] && (( $(vercmp "$new_pkgver" "$OLD_PKGVER") < 0 )); then + echo "::error::Refusing to publish $new_pkgver over $OLD_PKGVER; vercmp calls that a downgrade." + exit 1 + fi + + ## --printsrcinfo implies --ignorearch, so this emits every declared arch + ## even though the container itself is x86_64. + sudo -u builder makepkg --printsrcinfo > .SRCINFO + + ## The payloads are already here, so this verifies the checksums and + ## package() without re-downloading anything. + sudo -u builder makepkg --force --noconfirm --nodeps + + ## Advisory. Expect dependency-not-needed for the dlopened and spawned + ## deps, since namcap only reads ELF sonames. + if command -v namcap >/dev/null; then + namcap PKGBUILD ./*.pkg.tar.zst 2>&1 | tee namcap.txt || true + fi + + ## makepkg ran as another uid, so hand the bind mount back or the steps + ## after this cannot write in it. + chown -R "${HOST_UID}:${HOST_GID}" /work + ' + rm -f work/*.pkg.tar.zst + { + echo "### namcap" + echo '```' + cat work/namcap.txt 2>/dev/null || echo "namcap produced no output" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + rm -f work/namcap.txt + + ## The test-build above proves it builds. This installs it for real and runs + ## ldd, which is the only way a broken depends list shows up before users hit it. + ## No binfmt on these runners, so an aarch64 leg is rendered but not installed; + ## run validate.sh --with-binfmt locally when a release starts shipping one. + - name: Validate the package end to end + env: + TAG: ${{ steps.tag.outputs.tag }} + run: ./packaging/aur/validate.sh --tag "$TAG" --repo "$GITHUB_REPOSITORY" + + - name: Compare against the AUR + id: compare + env: + PKGNAME: ${{ steps.current.outputs.pkgname }} + run: | + set -euo pipefail + grep -qx "pkgbase = ${PKGNAME}" work/.SRCINFO \ + || { echo "::error::.SRCINFO pkgbase does not match ${PKGNAME}; the AUR would reject this push."; exit 1; } + + ## .SRCINFO is compared too. A matching PKGBUILD next to a stale .SRCINFO + ## is exactly the state that leaves every helper showing the old version. + if cmp -s work/PKGBUILD aur/PKGBUILD && cmp -s work/.SRCINFO aur/.SRCINFO \ + && cmp -s work/LICENSE aur/LICENSE; then + echo "The AUR already matches. Nothing to push." + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + ## Snapshot what we compared against, so the push job can detect an AUR + ## that moved while the run waited for environment approval. + cp aur/PKGBUILD work/PKGBUILD.baseline + { + echo "### AUR render" + echo '```' + grep -E '^(pkgname|pkgver|pkgrel|arch|source_|sha256sums_)' work/PKGBUILD + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - uses: actions/upload-artifact@v4 + with: + name: aur-package + ## upload-artifact drops dotfiles by default, which would lose .SRCINFO. + include-hidden-files: true + path: | + work/PKGBUILD + work/PKGBUILD.baseline + work/.SRCINFO + work/LICENSE + if-no-files-found: error + + # The only job that holds the key, and it runs no repo-controlled script. + push: + name: Push to the AUR + needs: render + if: needs.render.outputs.changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + # Add a required reviewer here under Settings -> Environments -> aur. An AUR key + # grants push to every package the account maintains, so this is worth doing. + environment: aur + steps: + - uses: actions/download-artifact@v4 + with: + name: aur-package + path: work + + - name: Commit and push + env: + KEY: ${{ secrets.ROBRIX_AUR_SSH_KEY }} + PKGNAME: ${{ needs.render.outputs.pkgname }} + TAG: ${{ needs.render.outputs.tag }} + PKGVER: ${{ needs.render.outputs.pkgver }} + PKGREL: ${{ needs.render.outputs.pkgrel }} + run: | + set -euo pipefail + if [[ -z "$KEY" ]]; then + echo "::error::ROBRIX_AUR_SSH_KEY is unset. It must hold the passphrase-less private key registered on the project's AUR account." + exit 1 + fi + + install -d -m 700 ~/.ssh + (umask 077; printf '%s\n' "$KEY" > ~/.ssh/aur) + ## Fails loudly on a mangled or passphrase-protected secret, and prints the + ## public half to /dev/null so nothing lands in the log either way. + ssh-keygen -y -P '' -f ~/.ssh/aur > /dev/null + + ## Pinned rather than ssh-keyscan'd at run time, so a poisoned DNS answer + ## cannot hand us its own host key. Published at + ## https://archlinux.org/news/aur-migration-new-ssh-hostkeys/ + cat > ~/.ssh/aur_known_hosts <<'EOF' + aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN + aur.archlinux.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLMiLrP8pVi5BFX2i3vepSUnpedeiewE5XptnUnau+ZoeUOPkpoCgZZuYfpaIQfhhJJI5qgnjJmr4hyJbe/zxow= + aur.archlinux.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKF9vAFWdgm9Bi8uc+tYRBmXASBb5cB5iZsB7LOWWFeBrLp3r14w0/9S2vozjgqY5sJLDPONWoTTaVTbhe3vwO8CBKZTEt1AcWxuXNlRnk9FliR1/eNB9uz/7y1R0+c1Md+P98AJJSJWKN12nqIDIhjl2S1vOUvm7FNY43fU2knIhEbHybhwWeg+0wxpKwcAd/JeL5i92Uv03MYftOToUijd1pqyVFdJvQFhqD4v3M157jxS5FTOBrccAEjT+zYmFyD8WvKUa9vUclRddNllmBJdy4NyLB8SvVZULUPrP3QOlmzemeKracTlVOUG1wsDbxknF1BwSCU7CmU6UFP90kpWIyz66bP0bl67QAvlIc52Yix7pKJPbw85+zykvnfl2mdROsaT8p8R9nwCdFsBc9IiD0NhPEHcyHRwB8fokXTajk2QnGhL+zP5KnkmXnyQYOCUYo3EKMXIlVOVbPDgRYYT/XqvBuzq5S9rrU70KoI/S5lDnFfx/+lPLdtcnnEPk= + EOF + chmod 600 ~/.ssh/aur_known_hosts + + export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/aur -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/aur_known_hosts" + git -c init.defaultBranch=master clone "ssh://aur@aur.archlinux.org/${PKGNAME}.git" aur + [[ -f aur/PKGBUILD ]] || { echo "::error::${PKGNAME} has no PKGBUILD over ssh; do the first manual push by hand."; exit 1; } + + ## The AUR may have moved while this run sat waiting for approval; pushing + ## a render made against the old state would silently revert that work. + cmp -s work/PKGBUILD.baseline aur/PKGBUILD \ + || { echo "::error::the AUR changed since this run rendered. Re-run the workflow against fresh state."; exit 1; } + cp work/PKGBUILD work/.SRCINFO work/LICENSE aur/ + ## The AUR hook rejects subdirectories and any root file over 250 KiB. + [[ -z "$(find aur -mindepth 1 -maxdepth 1 -type d ! -name .git)" ]] || { echo "::error::subdirectory in the AUR checkout"; exit 1; } + [[ -z "$(find aur -maxdepth 1 -type f -size +250k)" ]] || { echo "::error::a root-level file exceeds 250 KiB"; exit 1; } + + git -C aur add PKGBUILD .SRCINFO LICENSE + if git -C aur diff --cached --quiet; then + echo "Nothing staged after the copy. Nothing to push." + exit 0 + fi + git -C aur -c user.name='Project Robius' -c user.email='it@gosim.org' \ + commit -m "${PKGNAME} ${PKGVER}-${PKGREL} (${TAG})" + ## Plain push, never --force. A rejection means somebody else pushed, which + ## needs a human to look rather than an overwrite. + git -C aur push origin master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aa0f2626..c3643f395 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -247,8 +247,7 @@ jobs: set -euo pipefail TAG="${{ steps.resolve.outputs.tag }}" version="${TAG#v}" - pkgver="${version/-/}" - pkgver="${pkgver//-/.}" + pkgver="$(packaging/aur/render-pkgbuild.sh --print-pkgver --tag "$TAG")" notes="docs/RELEASE_NOTES_${TAG}.md" body="${RUNNER_TEMP}/release-body.md" if [[ -f "$notes" ]]; then @@ -343,7 +342,7 @@ jobs: ## .deb/.AppImage over anything else in the same group, which would silently drop ## the pacman payload. Clearing dist/ between passes keeps each upload to its own ## format. Only the built packages are removed, never dist/resources. - - name: Clear packaged artifacts before the next pass + - name: Clear packaged .deb before the next pass if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-22.04-arm' run: rm -f dist/*.deb @@ -391,16 +390,16 @@ jobs: asset_name_template: __APP__-__VERSION__-__ARCH__.__EXT__ uploadUpdaterJson: false - - name: Clear packaged artifacts before the next pass - if: matrix.os == 'ubuntu-22.04' + - name: Clear packaged .AppImage before the next pass + if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-22.04-arm' run: rm -f dist/*.AppImage ## This tarball isn't installable on its own, it's the payload an Arch PKGBUILD - ## unpacks. We turn it into a real package below, and the `robrix-bin` AUR package - ## fetches it by cargo-packager's own file name, so `__FILENAME__` keeps that - ## stable. Renaming it breaks the AUR package. + ## unpacks. We turn it into a real package below, and the AUR package fetches it + ## by cargo-packager's own file name, so `__FILENAME__` keeps that stable. + ## Renaming it breaks the AUR package. - name: Package pacman payload (desktop) - if: matrix.os == 'ubuntu-22.04' + if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-22.04-arm' uses: project-robius/makepad-packaging-action@v1.8.0 env: CARGO_PACKAGER_SIGNING_KEY: ${{ secrets.CARGO_PACKAGER_SIGNING_KEY }} @@ -412,51 +411,65 @@ jobs: asset_name_template: __FILENAME__ uploadUpdaterJson: false - ## Arch Linux is x86_64-only and `makepkg` only exists on Arch, so we build the - ## actual installable package in a container. Without this, an Arch user has no - ## one-command install: the payload above can't be fed to `pacman -U` directly. + ## `makepkg` only exists on Arch, so we build the actual installable package in + ## a container. Official Arch is x86_64-only, so the aarch64 leg uses an Arch + ## Linux ARM image, which is the distro that package is for anyway. + ## + ## No AUR push belongs in here. This job runs while the release is still a + ## draft, so its asset URLs 404 for everyone but the token holder. The AUR + ## PKGBUILD is pushed by .github/workflows/aur-publish.yml on + ## `release: published`, once a human has published this draft. - name: Build and upload Arch Linux package - if: matrix.os == 'ubuntu-22.04' && needs.create_release.outputs.tag != '' + if: (matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-22.04-arm') && needs.create_release.outputs.tag != '' env: GH_TOKEN: ${{ secrets.ROBRIX_RELEASE }} TAG: ${{ needs.create_release.outputs.tag }} + ARCH: ${{ matrix.arch }} run: | set -euo pipefail srcver="$(awk -F'"' '/^version[[:space:]]*=/ {print $2; exit}' Cargo.toml)" - payload="dist/robrix_${srcver}_x86_64.tar.gz" + payload="dist/robrix_${srcver}_${ARCH}.tar.gz" if [[ ! -f "$payload" ]]; then echo "::error::Expected pacman payload at $payload, but it isn't there." ls -la dist/ || true exit 1 fi - ## Drop the first hyphen only, so 1.0.0-alpha.2 becomes 1.0.0alpha.2, which - ## vercmp sorts *below* the eventual 1.0.0. Dotting it instead would sort above. - pkgver="${srcver/-/}" - pkgver="${pkgver//-/.}" - ## mktemp gives us 0700, but the container re-owns this to its own uid, which ## would leave the runner unable to read the package back out. build_dir="$(mktemp -d)" chmod 0755 "$build_dir" - cp "$payload" LICENSE-MIT "$build_dir"/ - sed -e "s|@SRCVER@|${srcver}|g" \ - -e "s|@PKGVER@|${pkgver}|g" \ - packaging/arch/PKGBUILD.in > "$build_dir/PKGBUILD" + cp "$payload" "$build_dir"/ + + ## Same template the AUR package is rendered from, so the two can't drift. + ## --mode local means bare filenames and SKIP sums, since nothing is fetched. + packaging/aur/render-pkgbuild.sh --mode local \ + --srcver "$srcver" --arch "$ARCH" --out "$build_dir" + + ## Both runners are native, so neither leg needs qemu. + if [[ "$ARCH" == "aarch64" ]]; then + image='menci/archlinuxarm:base-devel' + else + image='archlinux:base-devel' + fi ## makepkg refuses to run as root, hence the unprivileged build user. ## --nodeps because it otherwise resolves the runtime deps against the ## container, which has the toolchain only, and we're compiling nothing here. - docker run --rm -v "$build_dir:/build" -w /build archlinux:base-devel bash -euo pipefail -c ' - ## These images ship without the pacman lsign key. - pacman-key --init && pacman-key --populate archlinux + docker run --rm -v "$build_dir:/build" -w /build "$image" bash -euo pipefail -c ' + ## These images ship without the pacman lsign key. No keyring name, so this + ## covers the archlinux and archlinuxarm keyrings alike. + pacman-key --init && pacman-key --populate pacman -Syu --noconfirm --needed namcap || echo "::warning::namcap unavailable, skipping lint" + ## Arch Linux ARM has defaulted to a different PKGEXT before, and we want + ## one asset naming scheme across both arches. + printf "\nPKGEXT=.pkg.tar.zst\n" >> /etc/makepkg.conf useradd --create-home builder chown -R builder /build ## sudo drops the environment, so PACKAGER has to be passed through here, ## otherwise the package records "Unknown Packager". - sudo -u builder env PACKAGER="Project Robius " \ + sudo -u builder env PACKAGER="Project Robius " \ makepkg --force --noconfirm --nodeps ## Reports missing or surplus dependencies. Advisory only, so a lint ## warning never blocks a release. diff --git a/.gitignore b/.gitignore index de91482ce..b0ca4aba7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ testflight.sh ## Filled-in release secrets for upload-release-secrets.sh (never commit) /packaging/release-secrets.env + +## Rendered by packaging/aur/render-pkgbuild.sh, never committed. +packaging/aur/PKGBUILD +packaging/aur/.SRCINFO diff --git a/packaging/arch/PKGBUILD.in b/packaging/arch/PKGBUILD.in index 96eadc96d..a90f16738 100644 --- a/packaging/arch/PKGBUILD.in +++ b/packaging/arch/PKGBUILD.in @@ -1,25 +1,28 @@ -# Maintainer: Project Robius +# Maintainer: Project Robius # -# Template for the pacman package attached to each GitHub release, so Arch users can -# install Robrix with a single `pacman -U`. The release workflow fills in the two -# placeholders below and runs `makepkg` against it inside an Arch container. +# Shared template behind both Robrix pacman packages: the .pkg.tar.zst attached to +# every GitHub release, and the AUR package. Only packaging/aur/render-pkgbuild.sh +# fills these placeholders in, and the two renderings differ only in the source +# block, so send fixes here rather than to a rendered copy. # -# The source version is the raw crate version, which is what the payload tarball is -# named after. The package version is that with the pre-release hyphen removed, since -# pacman forbids hyphens in pkgver. It's removed rather than turned into a dot because -# vercmp sorts `1.0.0alpha.2` below `1.0.0` but `1.0.0.alpha.2` above it, which would -# make the eventual 1.0.0 release look like a downgrade. +# pkgver deletes the pre-release hyphen rather than dotting or underscoring it, +# because vercmp sorts `1.0.0alpha.2` below `1.0.0` while both `1.0.0.alpha.2` and +# `1.0.0_alpha.2` sort above it. The ArchWiki's generic "use an underscore" advice +# would make the real 1.0.0 look like a downgrade. -pkgname=robrix +pkgname=@PKGNAME@ pkgver=@PKGVER@ -pkgrel=1 -pkgdesc="A multi-platform Matrix chat client written in Rust, using the Makepad UI toolkit and the Robius app dev framework" -arch=('x86_64') -url="https://github.com/project-robius/robrix" -license=('MIT') +pkgrel=@PKGREL@ +pkgdesc="A powerful multi-platform Matrix chat client written from scratch in Rust" +arch=(@ARCH@) +url="@URL@" +## Only the ones with a license file to point at. usr/share/licenses/robrix/copyright +## enumerates the rest per file, with full texts, and namcap counts files against +## uncommon SPDX ids, so adding an id here means adding a file too. +license=('MIT' 'Apache-2.0' 'OFL-1.1' 'CC-BY-3.0') depends=( 'glibc' - 'gcc-libs' + 'libgcc' ## owns libgcc_s.so.1; gcc-libs is an empty metapackage now 'libx11' 'libxcursor' 'libxkbcommon' @@ -27,24 +30,29 @@ depends=( 'alsa-lib' 'libpulse' 'openssl' - ## The next three are all dlopen'd or spawned rather than linked, so nothing catches - ## them automatically. Missing libEGL is what made the .deb install fine and then die - ## with "can't load LibEGL"; dbus and xdg-utils fail the same way, just later. - 'libglvnd' ## libEGL.so.1, loaded by Makepad at startup - 'dbus' ## libdbus-1.so.3, loaded by the file picker - 'xdg-utils' ## xdg-open, spawned to open links and attachments - 'ca-certificates' + ## These four are dlopen'd, spawned, or read off disk rather than linked, so no + ## tooling finds them and namcap calls them unneeded. Don't drop them. + 'libglvnd' ## libEGL.so.1, loaded by Makepad at startup + 'dbus' ## libdbus-1.so.3, loaded by the file picker + 'xdg-utils' ## xdg-open, spawned to open links and attachments + 'ca-certificates' ## /etc/ssl/certs, read at runtime by both TLS stacks 'hicolor-icon-theme' ) -conflicts=('robrix-bin' 'robrix-git') -## We're packaging an already-built binary, so there's nothing to compile or strip. +## With neither of these the attach and save-as dialogs never open. +optdepends=( + 'xdg-desktop-portal-impl: native file picker, e.g. xdg-desktop-portal-gtk or -kde' + 'zenity: file picker fallback when no portal backend is running' +) +provides=(@PROVIDES@) +conflicts=(@CONFLICTS@) +## Nothing to compile here, and the binary upstream ships is already stripped. options=('!strip' '!debug') -## Both are copied in next to this file by the release workflow. -source=("robrix_@SRCVER@_x86_64.tar.gz" "LICENSE-MIT") -sha256sums=('SKIP' 'SKIP') +@SOURCES@ package() { + ## The payload unpacks straight to $srcdir/usr with no top-level directory, and + ## already carries usr/share/licenses/robrix/{copyright,THIRD-PARTY-NOTICES.html}. cp -a "${srcdir}/usr" "${pkgdir}/" - install -Dm644 "${srcdir}/LICENSE-MIT" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-MIT" +@LICENSE_DIR_FIXUP@ } diff --git a/packaging/aur/aur-repo-LICENSE b/packaging/aur/aur-repo-LICENSE new file mode 100644 index 000000000..b2bc3b3d3 --- /dev/null +++ b/packaging/aur/aur-repo-LICENSE @@ -0,0 +1,12 @@ +Copyright Project Robius + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packaging/aur/render-pkgbuild.sh b/packaging/aur/render-pkgbuild.sh new file mode 100755 index 000000000..dc3821ea9 --- /dev/null +++ b/packaging/aur/render-pkgbuild.sh @@ -0,0 +1,251 @@ +#!/usr/bin/env bash +# +# Renders packaging/arch/PKGBUILD.in into a real PKGBUILD. One template, two ways: +# +# --mode local the release build. The payload tarball already sits next to the +# PKGBUILD, so sources are bare filenames with SKIP checksums. +# --mode aur the AUR. Sources are release-asset URLs with real sha256s, and we +# ask the release which arch payloads it actually has, so a new arch +# turns itself on the first release that ships one. +# +# render-pkgbuild.sh --mode local --srcver 1.0.0-alpha.2 --arch x86_64 --out DIR +# render-pkgbuild.sh --mode aur --tag v1.0.0-alpha.2 [--repo owner/name] [--pkgrel N] --out DIR +# render-pkgbuild.sh --print-pkgname +# render-pkgbuild.sh --print-pkgver --tag v1.0.0-alpha.2 +# +# aur mode leaves each downloaded payload in --out under the name the PKGBUILD +# expects, so a following `makepkg` verifies the checksums without re-downloading. +# Progress goes to stderr, so stdout stays usable for the --print-* forms. +# +# Plain bash 3.2, so it runs on stock macOS as well as on Arch and the runners. + +set -euo pipefail + +## THE ONE-LINE NAME SWITCH. Set this to robrix-bin to publish under the -bin +## suffix instead; provides, conflicts and the license dir all follow it. +PKGNAME='robrix' + +## Upstream's own name, which is what cargo-packager names the payload after and +## what the payload's own /usr/lib and /usr/share/licenses paths use. +APPNAME='robrix' +DEFAULT_REPO='project-robius/robrix' +## Arches we know how to publish, in the order they get emitted. +CANDIDATE_ARCHES='x86_64 aarch64' + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +TEMPLATE="$SCRIPT_DIR/../arch/PKGBUILD.in" +NL=$'\n' + +log() { printf '%s\n' "$*" >&2; } +die() { printf 'Error: %s\n' "$*" >&2; exit 1; } +need() { [[ $2 -ge 2 ]] || die "$1 needs a value"; } + +mode='aur' srcver='' tag='' arch='' out='' pkgrel=1 print='' +repo="$DEFAULT_REPO" + +while (( $# )); do + case "$1" in + --mode) need "$1" $#; mode="$2"; shift 2 ;; + --srcver) need "$1" $#; srcver="$2"; shift 2 ;; + --tag) need "$1" $#; tag="$2"; shift 2 ;; + --arch) need "$1" $#; arch="$2"; shift 2 ;; + --repo) need "$1" $#; repo="$2"; shift 2 ;; + --pkgrel) need "$1" $#; pkgrel="$2"; shift 2 ;; + --out) need "$1" $#; out="$2"; shift 2 ;; + --print-pkgname) print='pkgname'; shift ;; + --print-pkgver) print='pkgver'; shift ;; + -h|--help) sed -n '2,20p' "$0" >&2; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +if [[ "$print" == 'pkgname' ]]; then + printf '%s\n' "$PKGNAME" + exit 0 +fi + +## Drop the first hyphen only, so 1.0.0-alpha.2 becomes 1.0.0alpha.2. The note at +## the top of PKGBUILD.in explains why it isn't a dot or an underscore. +to_pkgver() { + local v="${1/-/}" + printf '%s' "${v//-/.}" +} + +check_tag() { + [[ -n "$tag" ]] || die "--tag is required" + ## The emitted source URLs rebuild the tag as v${_srcver}, so anything else would + ## hash one URL and publish another. + [[ "$tag" == v* ]] || die "--tag must start with 'v', got '$tag'" +} + +if [[ "$print" == 'pkgver' ]]; then + check_tag + to_pkgver "${tag#v}" + printf '\n' + exit 0 +fi + +[[ "$mode" == 'aur' || "$mode" == 'local' ]] || die "--mode must be aur or local, got '$mode'" +[[ -n "$out" ]] || die "--out DIR is required" +[[ -f "$TEMPLATE" ]] || die "template not found at $TEMPLATE" +grep -q '@SOURCES@' "$TEMPLATE" || die "template lost its @SOURCES@ line" +grep -q '@LICENSE_DIR_FIXUP@' "$TEMPLATE" || die "template lost its @LICENSE_DIR_FIXUP@ line" +[[ "$pkgrel" =~ ^[1-9][0-9]*$ ]] || die "--pkgrel must be a positive integer, got '$pkgrel'" +mkdir -p "$out" + +sha256_of() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | cut -d' ' -f1 + else + shasum -a 256 "$1" | cut -d' ' -f1 + fi +} + +## One ranged GET, so an arch with no payload costs a 404 instead of 70 MB. curl +## already prints 000 itself when it can't connect, so no fallback echo here. +probe() { + curl -sSL -r 0-0 -o /dev/null -w '%{http_code}' \ + --retry 5 --retry-delay 3 --connect-timeout 20 --max-time 120 "$1" 2>/dev/null || true +} + +## Download to .part first, so an interrupted run can't leave a truncated file that +## we would then happily checksum. +download() { + curl -fL --no-progress-meter --retry 8 --retry-all-errors --retry-delay 3 \ + --connect-timeout 20 --max-time 1800 -o "${2}.part" "$1" + mv "${2}.part" "$2" +} + +sources='' +found='' + +if [[ "$mode" == 'local' ]]; then + [[ -n "$srcver" || -n "$tag" ]] || die "--mode local needs --srcver (or --tag)" + [[ -n "$srcver" ]] || { check_tag; srcver="${tag#v}"; } + ## uname says arm64 on macOS and aarch64 on Linux; makepkg only knows the latter. + [[ -n "$arch" ]] || arch="$(uname -m)" + [[ "$arch" == 'arm64' ]] && arch='aarch64' + case " $CANDIDATE_ARCHES " in + *" $arch "*) ;; + *) die "'$arch' is not one of the arches we package: $CANDIDATE_ARCHES" ;; + esac + found="$arch" + printf -v sources '%s\n' \ + '## Copied in next to this PKGBUILD by the release workflow, so nothing here' \ + '## came off the network and there is nothing to verify.' \ + "_srcver=${srcver}" \ + '' \ + "source_${arch}=(\"${APPNAME}_\${_srcver}_${arch}.tar.gz\")" \ + "sha256sums_${arch}=('SKIP')" + log "==> local: ${arch}, checksums skipped (it's a build artifact sitting right there)" +else + check_tag + srcver="${tag#v}" + baseurl="https://github.com/${repo}/releases/download/${tag}" + + arch_block='' + for a in $CANDIDATE_ARCHES; do + asset="${APPNAME}_${srcver}_${a}.tar.gz" + code="$(probe "${baseurl}/${asset}")" + case "$code" in + 200|206) ;; + 404) log " -- ${a}: no ${asset} on ${tag}, leaving that arch out"; continue ;; + *) die "probing ${baseurl}/${asset} returned HTTP ${code:-000}" ;; + esac + + dest="${out}/${asset}" + if [[ -f "$dest" ]]; then + log " .. ${a}: reusing ${asset} already in ${out}" + else + log " .. ${a}: fetching ${asset}" + download "${baseurl}/${asset}" "$dest" + fi + sum="$(sha256_of "$dest")" + log " ok ${a}: ${sum}" + + found="${found:+$found }${a}" + printf -v one '%s\n' \ + '' \ + "source_${a}=(\"\${_baseurl}/${APPNAME}_\${_srcver}_${a}.tar.gz\")" \ + "sha256sums_${a}=('${sum}')" + arch_block="${arch_block}${one}" + done + + [[ -n "$found" ]] || die "release ${tag} ships no ${APPNAME}_${srcver}_.tar.gz payload. +If it is still a draft, publish it first: draft assets 404 for anonymous downloads." + + printf -v sources '%s\n' \ + '## Named by cargo-packager and pinned by `asset_name_template: __FILENAME__`' \ + '## in release.yml, so renaming it there breaks this. The name already carries' \ + '## the version and arch, so it needs no `::` rename to stay unique.' \ + "_srcver=${srcver}" \ + "_baseurl=\"https://github.com/${repo}/releases/download/v\${_srcver}\"" + sources="${sources}${arch_block}" +fi + +## printf leaves a trailing newline the template already provides. +sources="${sources%"$NL"}" + +arches='' +for a in $found; do arches="${arches:+$arches }'${a}'"; done + +pkgver="$(to_pkgver "$srcver")" + +## Provide whichever prebuilt name we are not, so either package drops in for the +## other, and conflict with all three since they all own /usr/bin/robrix. +provides='' conflicts='' +for n in "$APPNAME" "${APPNAME}-bin"; do + [[ "$n" == "$PKGNAME" ]] || provides="${provides:+$provides }\"${n}=\${pkgver}\"" +done +for n in "$APPNAME" "${APPNAME}-bin" "${APPNAME}-git"; do + [[ "$n" == "$PKGNAME" ]] || conflicts="${conflicts:+$conflicts }'${n}'" +done + +## The payload names its license dir after the app; namcap wants it named after the +## package. Emitted only when those differ, so the rendered file has no dead branch. +fixup='' +if [[ "$PKGNAME" != "$APPNAME" ]]; then + printf -v fixup '%s\n' \ + '' \ + " ## namcap looks for the license files under \$pkgname. Nothing reads this" \ + " ## path at runtime, unlike /usr/lib/${APPNAME}, so renaming it is safe." \ + " mv \"\${pkgdir}/usr/share/licenses/${APPNAME}\" \"\${pkgdir}/usr/share/licenses/\${pkgname}\"" +fi + +## awk -v can't carry a newline portably, so the two multi-line blocks go via files. +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +printf '%s\n' "$sources" > "$tmp/sources" +printf '%s' "$fixup" > "$tmp/fixup" + +awk -v sourcesfile="$tmp/sources" -v fixupfile="$tmp/fixup" \ + -v pkgname="$PKGNAME" -v pkgver="$pkgver" -v pkgrel="$pkgrel" \ + -v arches="$arches" -v url="https://github.com/${repo}" \ + -v provides="$provides" -v conflicts="$conflicts" ' + index($0, "@SOURCES@") { + while ((getline line < sourcesfile) > 0) print line + close(sourcesfile); next + } + index($0, "@LICENSE_DIR_FIXUP@") { + while ((getline line < fixupfile) > 0) print line + close(fixupfile); next + } + { + gsub(/@PKGNAME@/, pkgname); gsub(/@PKGVER@/, pkgver); gsub(/@PKGREL@/, pkgrel) + gsub(/@ARCH@/, arches); gsub(/@URL@/, url) + gsub(/@PROVIDES@/, provides); gsub(/@CONFLICTS@/, conflicts) + print + } +' "$TEMPLATE" > "$tmp/PKGBUILD" + +left="$(grep -o '@[A-Z][A-Z_]*@' "$tmp/PKGBUILD" | sort -u | tr '\n' ' ' || true)" +[[ -z "$left" ]] || die "unsubstituted placeholder(s): ${left}" +bash -n "$tmp/PKGBUILD" || die "the rendered PKGBUILD is not valid shell" + +cp "$tmp/PKGBUILD" "${out}/PKGBUILD" +log "==> Wrote ${out}/PKGBUILD (pkgname=${PKGNAME} pkgver=${pkgver} pkgrel=${pkgrel} arch=${found})" + +printf 'pkgname=%s\n' "$PKGNAME" +printf 'pkgver=%s\n' "$pkgver" +printf 'pkgrel=%s\n' "$pkgrel" +printf 'arches=%s\n' "$found" diff --git a/packaging/aur/validate.sh b/packaging/aur/validate.sh new file mode 100755 index 000000000..7ff396013 --- /dev/null +++ b/packaging/aur/validate.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +# +# End-to-end check of the Robrix AUR package, on any Linux box with docker. +# +# ./packaging/aur/validate.sh --tag v1.0.0-alpha.2 +# ./packaging/aur/validate.sh --tag v1.0.0-alpha.3 --with-binfmt +# +# It renders the PKGBUILD once, then per arch builds it with makepkg in an Arch +# container, lints it with namcap, installs the result with pacman -U so the real +# dependency resolution runs, and runs ldd on the installed binary. x86_64 is +# native on an x86_64 host; aarch64 needs qemu/binfmt there (--with-binfmt), and +# is skipped with a clear message when the release ships no aarch64 payload, which +# is the case for every release up to and including v1.0.0-alpha.2. +# +# Everything lands in one mktemp dir. The only thing touched outside it is the +# binfmt registration, and only with --with-binfmt. +# +# Also: --repo owner/name, --arch 'x86_64 aarch64', --keep (leave the work dir). + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +RENDER="$SCRIPT_DIR/render-pkgbuild.sh" + +tag='' +repo='project-robius/robrix' +arches='x86_64 aarch64' +with_binfmt=0 +keep=0 + +pass=0; fail=0; skip=0 +results='' + +say() { printf '%s\n' "$*"; } +note() { printf '\n\033[1m== %s\033[0m\n' "$*"; } +ok() { pass=$(( pass + 1 )); results="${results}PASS $*"$'\n'; printf '\033[32mPASS\033[0m %s\n' "$*"; } +bad() { fail=$(( fail + 1 )); results="${results}FAIL $*"$'\n'; printf '\033[31mFAIL\033[0m %s\n' "$*"; } +meh() { skip=$(( skip + 1 )); results="${results}SKIP $*"$'\n'; printf '\033[33mSKIP\033[0m %s\n' "$*"; } +die() { printf 'Error: %s\n' "$*" >&2; exit 1; } + +while (( $# )); do + case "$1" in + --tag) [[ $# -ge 2 ]] || die "--tag needs a value"; tag="$2"; shift 2 ;; + --repo) [[ $# -ge 2 ]] || die "--repo needs a value"; repo="$2"; shift 2 ;; + --arch) [[ $# -ge 2 ]] || die "--arch needs a value"; arches="$2"; shift 2 ;; + --with-binfmt) with_binfmt=1; shift ;; + --keep) keep=1; shift ;; + -h|--help) sed -n '2,18p' "$0"; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +[[ -n "$tag" ]] || die "pass --tag vX.Y.Z, e.g. --tag v1.0.0-alpha.2" +[[ -x "$RENDER" ]] || die "$RENDER is missing or not executable" +command -v docker >/dev/null 2>&1 || die "docker is required" +docker info >/dev/null 2>&1 || die "the docker daemon is not reachable from here" + +image_for() { + case "$1" in + ## Official Arch is x86_64 only, so aarch64 uses Arch Linux ARM, which is + ## the distro that package is for anyway. + x86_64) printf 'archlinux:base-devel' ;; + aarch64) printf 'menci/archlinuxarm:base-devel' ;; + *) return 1 ;; + esac +} +docker_platform_for() { + case "$1" in + x86_64) printf 'linux/amd64' ;; + aarch64) printf 'linux/arm64' ;; + esac +} + +work="$(mktemp -d)" +cleanup() { + ## makepkg runs as an unprivileged uid inside the container and leaves src/ and + ## pkg/ owned by it, so hand them back before trying to delete anything. + docker run --rm -v "$work:/w" "$(image_for "${host_arch:-x86_64}")" \ + chown -R "$(id -u):$(id -g)" /w >/dev/null 2>&1 || true + if (( keep )); then + printf '\nLeft everything in %s\n' "$work" + else + rm -rf "$work" + fi +} +trap cleanup EXIT + +host_arch="$(uname -m)" +[[ "$host_arch" == 'arm64' ]] && host_arch='aarch64' +pkgname="$("$RENDER" --print-pkgname)" +say "Working in $work" +say "Host $host_arch, package $pkgname, tag $tag" + +if (( with_binfmt )); then + note "Registering qemu binfmt handlers" + say "This needs --privileged because it writes system-wide binfmt_misc entries." + say "It replaces any existing qemu-user-static handlers here, until reboot." + say "What is registered now: $(ls /proc/sys/fs/binfmt_misc/ 2>/dev/null | tr '\n' ' ')" + docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 +fi + +# Rendered once. --mode aur emits every arch the release actually has, so the +# per-arch loop below just picks out the payload it needs. +note "Rendering from $tag" +render_dir="$work/render" +mkdir -p "$render_dir" +if ! "$RENDER" --mode aur --tag "$tag" --repo "$repo" --out "$render_dir" 2>&1 | sed 's/^/ /'; then + bad "rendering failed, nothing else can run" + printf '\n%s\nRESULT: FAIL\n' "$results" + exit 1 +fi +ok "rendered $render_dir/PKGBUILD" +grep -E '^(pkgname|pkgver|pkgrel|arch|license)=' "$render_dir/PKGBUILD" | sed 's/^/ /' + +for arch in $arches; do + note "$arch" + + if ! image="$(image_for "$arch")"; then + bad "$arch: not an arch this script knows how to build" + continue + fi + if ! grep -q "^source_${arch}=" "$render_dir/PKGBUILD"; then + meh "$arch: release $tag ships no ${arch} payload, so the PKGBUILD does not declare it" + continue + fi + + platform=() + if [[ "$arch" != "$host_arch" ]]; then + if [[ ! -e "/proc/sys/fs/binfmt_misc/qemu-${arch}" ]]; then + say " $arch is foreign to this $host_arch host and qemu-$arch is not registered." + say " Re-run with --with-binfmt, or run this on an $arch machine." + meh "$arch: emulation not available" + continue + fi + platform=(--platform "$(docker_platform_for "$arch")") + say " Running $image emulated. Expect it to be slow." + fi + + out="$work/$arch" + mkdir -p "$out" + cp "$render_dir/PKGBUILD" "$out/" + ## makepkg only wants the payload for its own CARCH, so copy just that one. + cp "$render_dir"/robrix_*_"${arch}".tar.gz "$out/" + + cat > "$out/run.sh" <<'INNER' +#!/bin/bash +set -euo pipefail +cd /work + +echo "--- container ---" +( source /etc/makepkg.conf; echo "CARCH=$CARCH" ) +pacman-key --init >/dev/null 2>&1 +pacman-key --populate >/dev/null 2>&1 +## One asset naming scheme across both images; ALARM has differed from Arch here. +printf '\nPKGEXT=.pkg.tar.zst\n' >> /etc/makepkg.conf +pacman -Syu --noconfirm --needed namcap >/dev/null + +echo "--- vercmp: 1.0.0alpha.2 must sort BELOW 1.0.0, the other two must not ---" +for v in 1.0.0alpha.2 1.0.0.alpha.2 1.0.0_alpha.2; do + printf ' %-14s vs 1.0.0 -> %s\n' "$v" "$(vercmp "$v" 1.0.0)" +done +[[ "$(vercmp 1.0.0alpha.2 1.0.0)" -lt 0 ]] || echo "VERCMP-WRONG" + +useradd --create-home builder +chown -R builder /work + +echo "--- .SRCINFO ---" +sudo -u builder makepkg --printsrcinfo > .SRCINFO +grep -E 'pkgbase|pkgver|pkgrel|arch =|source_|sha256sums_' .SRCINFO | sed 's/^/ /' + +echo "--- namcap PKGBUILD ---" +namcap PKGBUILD | tee namcap-pkgbuild.txt || true + +echo "--- makepkg ---" +sudo -u builder env PACKAGER="Robrix validate.sh " \ + makepkg --force --noconfirm --nodeps + +pkg="$(ls ./*.pkg.tar.zst)" +pkgname="$(sed -n 's/^pkgname=//p' PKGBUILD)" + +echo "--- namcap $pkg ---" +namcap "$pkg" | tee namcap-pkg.txt || true +if grep -q ' E: ' namcap-pkg.txt namcap-pkgbuild.txt; then + echo "NAMCAP-ERRORS-PRESENT" +fi + +echo "--- pacman -U, which resolves the depends array for real ---" +pacman -U --noconfirm "$pkg" +pacman -Qi "$pkgname" | sed -n '1,10p' | sed 's/^/ /' + +echo "--- installed files ---" +for f in /usr/bin/robrix /usr/share/applications/robrix.desktop \ + "/usr/share/licenses/${pkgname}/copyright" \ + "/usr/share/licenses/${pkgname}/THIRD-PARTY-NOTICES.html" \ + /usr/lib/robrix/robrix/resources; do + if [[ -e "$f" ]]; then echo " ok $f"; else echo " MISSING $f"; echo "FILES-MISSING"; fi +done + +echo "--- ldd /usr/bin/robrix ---" +ldd /usr/bin/robrix | sed 's/^/ /' +if ldd /usr/bin/robrix | grep -q 'not found'; then + echo "LDD-MISSING-LIBS" +fi + +echo "--- the dlopened and spawned deps namcap cannot see ---" +for so in libEGL.so.1 libdbus-1.so.3; do + if ldconfig -p | grep -q "$so"; then echo " ok $so"; else echo " MISSING $so"; echo "DLOPEN-MISSING"; fi +done +if command -v xdg-open >/dev/null; then echo " ok xdg-open"; else echo " MISSING xdg-open"; echo "DLOPEN-MISSING"; fi +if [[ -e /etc/ssl/certs/ca-certificates.crt ]]; then echo " ok ca-certificates"; else echo " MISSING ca-certificates"; echo "DLOPEN-MISSING"; fi + +echo "CONTAINER-OK" +INNER + chmod +x "$out/run.sh" + + log="$out/container.log" + if ! docker run --rm ${platform[@]+"${platform[@]}"} -v "$out:/work" -w /work "$image" \ + bash /work/run.sh > "$log" 2>&1; then + sed 's/^/ /' "$log" | tail -40 + bad "$arch: the container run failed, full log at $log" + continue + fi + sed 's/^/ /' "$log" + + problem='' + saw() { grep -q "$1" "$log"; } + saw 'CONTAINER-OK' || problem='the container did not finish' + if saw 'VERCMP-WRONG'; then problem='vercmp does not order the pkgver as documented'; fi + if saw 'LDD-MISSING-LIBS'; then problem='ldd reports missing libraries'; fi + if saw 'FILES-MISSING'; then problem='an expected installed file is absent'; fi + if saw 'DLOPEN-MISSING'; then problem='a dlopened or spawned dependency is not installed'; fi + if saw 'NAMCAP-ERRORS-PRESENT'; then problem='namcap reported an E:'; fi + if [[ -n "$problem" ]]; then + bad "$arch: $problem (see $log)" + else + ok "$arch: rendered, built, linted, installed, ldd clean" + fi +done + +note "Summary" +printf '%s' "$results" +printf '\n%d passed, %d failed, %d skipped\n' "$pass" "$fail" "$skip" +if (( fail )); then say "RESULT: FAIL"; exit 1; fi +if (( pass < 2 )); then + say "RESULT: FAIL (only the render was validated, no package was actually built)" + exit 1 +fi +say "RESULT: PASS"