diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a3b8a770..59238ed5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,7 +4,9 @@ on: pull_request: push: branches: - - main + - main + tags: + - 'v*' schedule: - cron: '17 14 * * *' @@ -46,6 +48,53 @@ jobs: name: try-dist.tgz path: try-*.tgz + - name: Package packaging assets + run: | + tar czf packaging.tgz packaging + + - name: Upload packaging assets + uses: actions/upload-artifact@v7 + with: + name: packaging-assets + path: packaging.tgz + + package: + needs: [dist, test-dist] + runs-on: ubuntu-latest + + steps: + - name: Install dependencies + run: | + sudo apt-get install -y rpm + + - name: Download dist tarball + uses: actions/download-artifact@v8 + with: + name: try-dist.tgz + + - name: Download packaging assets + uses: actions/download-artifact@v8 + with: + name: packaging-assets + + - name: Unpack tarballs + run: | + tar xzf try-*.tgz --strip-components=1 + tar xzf packaging.tgz + rm -f try-*.tgz packaging.tgz + + - name: Build .deb and .rpm + run: | + packaging/build_packages.sh + + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: try-packages + path: | + dist-packages/try*.deb + dist-packages/try*.rpm + test-dist: needs: dist strategy: @@ -65,10 +114,11 @@ jobs: - name: Download dist tarball uses: actions/download-artifact@v8 + with: + name: try-dist.tgz - name: Unpack tarball; configure and build utilities run: | - ls -lR tar xzf try-*.tgz --strip-components=1 rm try-*.tgz ./configure --disable-utils @@ -225,6 +275,103 @@ jobs: run: | nix-shell --run "sh scripts/run_tests.sh" + tag-version-check: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Check tag against declared versions + run: scripts/check_version.sh "${GITHUB_REF_NAME#v}" + + smoke-test-deb: + needs: package + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: try-packages + + - name: Install the .deb + run: | + sudo apt-get update + sudo apt-get install -y ./try*.deb + + - name: Check installed version matches the tag + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + # try -v writes to stderr + INSTALLED="$(try -v 2>&1 | awk '{print $NF}')" + echo "installed: '$INSTALLED' expected: '$TAG_VERSION'" + [ "$INSTALLED" = "$TAG_VERSION" ] + + - name: Check manpage is installed + run: | + man -w try + + smoke-test-rpm: + needs: package + runs-on: ubuntu-latest + container: fedora:latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: try-packages + + - name: Install the .rpm + run: | + # fedora's container image sets tsflags=nodocs by default, which + # would silently drop the manpage; override it for this install + dnf install -y --setopt=tsflags= ./try*.rpm man-db + + - name: Check installed version matches the tag + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + # try -v writes to stderr + INSTALLED="$(try -v 2>&1 | awk '{print $NF}')" + echo "installed: '$INSTALLED' expected: '$TAG_VERSION'" + [ "$INSTALLED" = "$TAG_VERSION" ] + + - name: Check manpage is installed + run: | + mandb + man -w try + + release: + needs: [tag-version-check, smoke-test-deb, smoke-test-rpm] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + + steps: + - name: Download dist tarball + uses: actions/download-artifact@v8 + with: + name: try-dist.tgz + + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: try-packages + + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "$GITHUB_REF_NAME" \ + --repo "$GITHUB_REPOSITORY" \ + --title "$GITHUB_REF_NAME" \ + --generate-notes \ + try-*.tgz ./try*.deb ./try*.rpm prerelease: needs: @@ -234,17 +381,24 @@ jobs: - lint - shellcheck - trycase-check + - package runs-on: ubuntu-latest if: ${{ github.ref == 'refs/heads/main' }} steps: - - name: Download binaries - uses: actions/download-artifact@v4 + - name: Download dist tarball + uses: actions/download-artifact@v8 + with: + name: try-dist.tgz + + - name: Download packages + uses: actions/download-artifact@v8 + with: + name: try-packages - name: Rename tarball run: | - mv try-dist.tgz/try-*.tgz try-latest.tgz - rmdir try-dist.tgz + mv try-*.tgz try-latest.tgz - name: Deploy 'latest' release uses: "marvinpinto/action-automatic-releases@latest" @@ -255,3 +409,5 @@ jobs: title: "Latest distribution tarball" files: | try-latest.tgz + try*.deb + try*.rpm diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 00000000..3c2354db --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,24 @@ +This directory holds the packaging logic used to build distribution packages for `try`. + +`build_packages.sh` builds a `.deb` and a `.rpm` from an unpacked `make dist` tarball, using +the native `dpkg-deb` and `rpmbuild` tools. On Debian/Ubuntu, `dpkg-deb` ships as part of the +base `dpkg` package; `rpmbuild` comes from the `rpm` package, which is not installed by +default and needs `apt-get install rpm` (or the equivalent) first. + +# Building + +From an unpacked `make dist` tarball, on a Debian/Ubuntu machine (or any environment with the +tools above): + +``` +packaging/build_packages.sh +``` + +Packages land in `dist-packages/`. + +Both packages are built from a single staged tree. + +# Why there is no Homebrew formula +`try` is Linux-only (Linux 5.11 or later). + +For distributions outside the deb/rpm families, `../package.nix` and `../shell.nix` provide a Nix package, Arch Linux users can install the [AUR package](https://aur.archlinux.org/packages/try), and building from source with `./configure && make && make install` is supported everywhere. diff --git a/packaging/build_packages.sh b/packaging/build_packages.sh new file mode 100755 index 00000000..b447f684 --- /dev/null +++ b/packaging/build_packages.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +# Builds .deb and .rpm packages from an unpacked try dist tree using +# native packaging tools (dpkg-deb, rpmbuild), against the tarball +# produced by `make dist`. + +set -e + +VERSION=$(grep '#define TRY_VERSION' utils/version.h | cut -d'"' -f2) +ARCH=$(dpkg --print-architecture) +RPMARCH=$(uname -m) +PKGROOT="$PWD/pkgroot" +OUTDIR="$PWD/dist-packages" +RPMTOPDIR="$PWD/rpmbuild" + +DESCRIPTION="Lets you run a command and inspect its effects before changing your live system" + +for d in "$PKGROOT" "$OUTDIR" "$RPMTOPDIR"; do + if [ -e "$d" ]; then + echo "error: $d already exists; refusing to overwrite" >&2 + exit 1 + fi +done +mkdir -p "$OUTDIR" + +grep -q '^AC_DEFUN(\[TRY_REQUIRE_PROG\], \[\])$' configure.ac || + sed -i '/^AC_DEFUN(\[TRY_REQUIRE_PROG\]/,/^])$/c\AC_DEFUN([TRY_REQUIRE_PROG], [])' configure.ac +autoconf + +./configure +make +make install prefix="$PKGROOT/usr" + +# build .deb with dpkg-deb +build_deb() { + output="$1" + + mkdir -p "$PKGROOT/DEBIAN" + size=$(du -sk "$PKGROOT/usr" | cut -f1) + + cat > "$PKGROOT/DEBIAN/control" < +Homepage: https://github.com/binpash/try +Description: $DESCRIPTION +EOF + + dpkg-deb --build --root-owner-group "$PKGROOT" "$output" +} + +# build .rpm with rpmbuild +build_rpm() { + output="$1" + + mkdir -p "$RPMTOPDIR/BUILD" "$RPMTOPDIR/RPMS" "$RPMTOPDIR/SOURCES" \ + "$RPMTOPDIR/SPECS" "$RPMTOPDIR/SRPMS" "$RPMTOPDIR/BUILDROOT" + + filelist=$(cd "$PKGROOT" && find usr \( -type f -o -type l \) | sed 's|^|/|') + + { + echo "Name: try" + echo "Version: $VERSION" + echo "Release: 1" + echo "Summary: $DESCRIPTION" + echo "License: MIT" + echo "URL: https://github.com/binpash/try" + echo "BuildArch: $RPMARCH" + echo "Requires: attr" + echo "%undefine _enable_debug_packages" + echo "%global debug_package %{nil}" + echo "%global __os_install_post %{nil}" + echo "%global _build_id_links none" + echo "" + echo "%description" + echo "$DESCRIPTION" + echo "" + echo "%install" + echo "cp -a '$PKGROOT'/usr %{buildroot}/usr" + echo "" + echo "%files" + echo "$filelist" + } > "$RPMTOPDIR/SPECS/try.spec" + + rpmbuild --define "_topdir $RPMTOPDIR" -bb "$RPMTOPDIR/SPECS/try.spec" + cp "$RPMTOPDIR/RPMS/$RPMARCH"/*.rpm "$output" +} + +build_deb "$OUTDIR/try_${VERSION}_$ARCH.deb" +build_rpm "$OUTDIR/try-$VERSION-1.$RPMARCH.rpm" diff --git a/scripts/check_version.sh b/scripts/check_version.sh index 566ca0f4..ef53981e 100755 --- a/scripts/check_version.sh +++ b/scripts/check_version.sh @@ -1,19 +1,29 @@ #!/bin/sh +REFERENCE_VERSION="$1" + SCRIPT_VERSION="$(grep 'TRY_VERSION=' try | cut -d'"' -f 2)" MANPAGE_VERSION="$(grep 'TRY(1)' docs/try.1.md | cut -d' ' -f 4)" INCLUDE_VERSION="$(grep '#define TRY_VERSION' utils/version.h | cut -d'"' -f 2)" CONFIGAC_VERSION="$(grep AC_INIT configure.ac | cut -d'[' -f3 | cut -d']' -f1)" -if [ "$SCRIPT_VERSION" = "$MANPAGE_VERSION" ] && - [ "$SCRIPT_VERSION" = "$INCLUDE_VERSION" ] && - [ "$SCRIPT_VERSION" = "$CONFIGAC_VERSION" ] +if [ -z "$REFERENCE_VERSION" ] +then + REFERENCE_VERSION="$SCRIPT_VERSION" +fi + +if [ "$SCRIPT_VERSION" = "$REFERENCE_VERSION" ] && + [ "$MANPAGE_VERSION" = "$REFERENCE_VERSION" ] && + [ "$INCLUDE_VERSION" = "$REFERENCE_VERSION" ] && + [ "$CONFIGAC_VERSION" = "$REFERENCE_VERSION" ] then - printf "\033[32;1m✓ VERSIONS MATCH (%s) \033[0m\n" "$SCRIPT_VERSION" + printf "\033[32;1m✓ VERSIONS MATCH (%s) \033[0m\n" "$REFERENCE_VERSION" else - echo " SCRIPT_VERSION = '$SCRIPT_VERSION'" - echo " MANPAGE_VERSION = '$MANPAGE_VERSION'" - echo " INCLUDE_VERSION = '$INCLUDE_VERSION'" - echo "CONFIGAC_VERSION = '$CONFIGAC_VERSION'" + [ -n "$1" ] && echo "EXPECTED_VERSION = '$REFERENCE_VERSION'" + echo " SCRIPT_VERSION = '$SCRIPT_VERSION'" + echo " MANPAGE_VERSION = '$MANPAGE_VERSION'" + echo " INCLUDE_VERSION = '$INCLUDE_VERSION'" + echo " CONFIGAC_VERSION = '$CONFIGAC_VERSION'" printf "\n❌ \033[31;1mVERSIONS DO NOT MATCH\033[0m\n" + exit 1 fi