diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e86af72..4a5c9ee 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -105,6 +105,12 @@ jobs: dry-run: true dry-run-intended: true + - name: Build .deb and .rpm + if: endsWith(matrix.target, '-musl') + env: + TARGET: ${{ matrix.target }} + run: sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "$SOULSEEK_RS_VERSION" "$TARGET" . soulseek-rs-nightly + - name: Store packaged binary uses: actions/upload-artifact@v4 with: @@ -112,6 +118,8 @@ jobs: path: | ${{ steps.package.outputs.tar || steps.package.outputs.zip }} ${{ steps.package.outputs.sha256 }} + soulseek-rs-nightly-*.deb + soulseek-rs-nightly-*.rpm if-no-files-found: error retention-days: 1 @@ -147,6 +155,10 @@ jobs: soulseek-rs-nightly-aarch64-apple-darwin.sha256 soulseek-rs-nightly-x86_64-pc-windows-msvc.sha256 soulseek-rs-nightly-aarch64-pc-windows-msvc.sha256 + soulseek-rs-nightly-amd64.deb + soulseek-rs-nightly-arm64.deb + soulseek-rs-nightly-x86_64.rpm + soulseek-rs-nightly-aarch64.rpm ) printf '%s\n' "${expected[@]}" | sort > "$RUNNER_TEMP/expected-assets" find dist -maxdepth 1 -type f -exec basename {} \; | sort > "$RUNNER_TEMP/actual-assets" @@ -234,6 +246,7 @@ jobs: timeout-minutes: 5 env: VERSION: ${{ needs.source.outputs.version }} + DOWNLOAD: https://github.com/${{ github.repository }}/releases/download/nightly steps: - uses: actions/checkout@v4 with: @@ -260,3 +273,19 @@ jobs: PATH="$RUNNER_TEMP/wget-path" /bin/sh website/public/install.sh --nightly "$SOULSEEK_RS_INSTALL_DIR/soulseek-rs" --version | grep -F "$VERSION" + - name: Install the .deb + run: | + curl -fsSLO "$DOWNLOAD/soulseek-rs-nightly-amd64.deb" + sudo apt-get install -y ./soulseek-rs-nightly-amd64.deb + soulseek-rs --version | grep -F "$VERSION" + dpkg -L soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz + dpkg -L soulseek-rs | grep -Fx /usr/share/zsh/vendor-completions/_soulseek-rs + + - name: Install the .rpm + run: | + docker run --rm fedora:latest sh -ec " + dnf install -y '$DOWNLOAD/soulseek-rs-nightly-x86_64.rpm' + soulseek-rs --version | grep -F '$VERSION' + rpm -ql soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz + rpm -ql soulseek-rs | grep -Fx /usr/share/zsh/site-functions/_soulseek-rs + " diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 0000000..99da052 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,67 @@ +name: Packaging + +on: + pull_request: + paths: + - 'Cargo.lock' + - 'Cargo.toml' + - 'packaging/**' + - 'scripts/package-linux.sh' + - 'soulseek-rs/src/cli.rs' + - 'soulseek-rs/src/run.rs' + - 'soulseek-rs/src/commands/**' + - '.github/workflows/packaging.yml' + +permissions: + contents: read + +concurrency: + group: packaging-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + VERSION: 0.0.0+git202601010000.0000000 + +jobs: + packages: + name: .deb and .rpm + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-musl + + - uses: Swatinem/rust-cache@v2 + + - name: Install musl toolchain + run: sudo apt-get update && sudo apt-get install -y musl-tools + + - name: Build the packages + env: + SOULSEEK_RS_VERSION: ${{ env.VERSION }} + TARGET: x86_64-unknown-linux-musl + run: | + cargo build --locked --release --target "$TARGET" --package soulseek-rs + sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "$VERSION" "$TARGET" . soulseek-rs-nightly + + - name: Install the .deb + run: | + sudo apt-get install -y ./soulseek-rs-nightly-amd64.deb + soulseek-rs --version | grep -F "$VERSION" + dpkg -L soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz + dpkg -L soulseek-rs | grep -Fx /usr/share/zsh/vendor-completions/_soulseek-rs + + - name: Install the .rpm + run: | + docker run --rm -v "$PWD:/packages:ro" fedora:latest sh -ec " + dnf install -y /packages/soulseek-rs-nightly-x86_64.rpm + soulseek-rs --version | grep -F '$VERSION' + rpm -ql soulseek-rs | grep -Fx /usr/share/man/man1/soulseek-rs.1.gz + rpm -ql soulseek-rs | grep -Fx /usr/share/zsh/site-functions/_soulseek-rs + " diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac7f993..a7bbc65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -200,6 +200,16 @@ jobs: ref: refs/tags/${{ needs.release.outputs.tag }} token: ${{ secrets.GITHUB_TOKEN }} + - name: Build and upload .deb and .rpm + if: endsWith(matrix.target, '-musl') && hashFiles('scripts/package-linux.sh') != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.release.outputs.tag }} + TARGET: ${{ matrix.target }} + run: | + sh scripts/package-linux.sh "target/$TARGET/release/soulseek-rs" "${TAG#v}" "$TARGET" dist + gh release upload "$TAG" dist/* --clobber + # The tap regenerates its formula from whatever `gh release view` calls latest, # so it only needs a nudge once every archive and .sha256 is on the release — # hence `needs: binaries` rather than firing alongside it. The tap also polls diff --git a/RELEASE.md b/RELEASE.md index b6356d0..941db0f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -28,6 +28,11 @@ Releases are automated by [release-plz](https://release-plz.dev). You do not run | `soulseek-rs-vX.Y.Z-x86_64-pc-windows-msvc.zip` | | | `soulseek-rs-vX.Y.Z-aarch64-pc-windows-msvc.zip` | | + - packages the two Linux binaries as `soulseek-rs-amd64.deb`, `soulseek-rs-arm64.deb`, + `soulseek-rs-x86_64.rpm` and `soulseek-rs-aarch64.rpm`, with the man page and the + bash, zsh and fish completions. The names carry no version, so + `releases/latest/download/soulseek-rs-amd64.deb` is a link that keeps working, + - rewrites `Formula/soulseek-rs.rb` in the `michel/homebrew-tap` repository so `brew install michel/tap/soulseek-rs` picks up the new macOS and Linux archives, - merges `master` back into `develop`, so the branch the work happens on carries the @@ -44,9 +49,15 @@ conflict. `.github/workflows/nightly.yml` builds the tip of `develop` at 02:17 UTC every day. It resolves the commit once, builds the same six targets as a numbered -release, verifies all twelve archives and checksum files as one set, then -replaces the mutable `nightly` prerelease and tag. The published release is -smoke-tested through both the curl and wget installer paths. +release, packages the two Linux ones as `soulseek-rs-nightly-*.deb` and `.rpm`, +verifies all sixteen files as one set, then replaces the mutable `nightly` +prerelease and tag. Every nightly binary reports `X.Y.Z+git.` +instead of the crate version. The published release is smoke-tested through the +curl and wget installer paths, and by installing the `.deb` and the `.rpm`. + +Never enable GitHub's Immutable Releases on this repository: nightly replaces +its assets and moves its tag, and a tag name burnt by that setting cannot be +reused. Use **Actions → Nightly → Run workflow** to force a build. The workflow always checks out `develop`, regardless of the ref selected in the dispatch form. The diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml new file mode 100644 index 0000000..1493db5 --- /dev/null +++ b/packaging/nfpm.yaml @@ -0,0 +1,55 @@ +name: soulseek-rs +arch: ${PKG_ARCH} +platform: linux +version: ${PKG_VERSION} +version_schema: none +release: "1" +section: net +priority: optional +maintainer: Michel de Graaf +description: | + Soulseek client for the terminal + A TUI, a scriptable command line and a daemon for the Soulseek + peer-to-peer network. +vendor: Michel de Graaf +homepage: https://re-invention.nl/soulseek-rs/ +license: MIT +suggests: + - gnome-keyring +contents: + - src: target/nfpm/soulseek-rs + dst: /usr/bin/soulseek-rs + file_info: + mode: 0755 + - src: target/nfpm/soulseek-rs.1.gz + dst: /usr/share/man/man1/soulseek-rs.1.gz + - src: target/nfpm/soulseek-rs.bash + dst: /usr/share/bash-completion/completions/soulseek-rs + - src: target/nfpm/soulseek-rs.fish + dst: /usr/share/fish/vendor_completions.d/soulseek-rs.fish + - src: target/nfpm/_soulseek-rs + dst: /usr/share/zsh/vendor-completions/_soulseek-rs + packager: deb + - src: target/nfpm/_soulseek-rs + dst: /usr/share/zsh/site-functions/_soulseek-rs + packager: rpm + - dst: /usr/share/doc/soulseek-rs + type: dir + packager: rpm + - dst: /usr/share/licenses/soulseek-rs + type: dir + packager: rpm + - src: README.md + dst: /usr/share/doc/soulseek-rs/README.md + packager: deb + - src: README.md + dst: /usr/share/doc/soulseek-rs/README.md + packager: rpm + type: doc + - src: LICENSE + dst: /usr/share/doc/soulseek-rs/copyright + packager: deb + - src: LICENSE + dst: /usr/share/licenses/soulseek-rs/LICENSE + packager: rpm + type: license diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh new file mode 100755 index 0000000..8216098 --- /dev/null +++ b/scripts/package-linux.sh @@ -0,0 +1,54 @@ +#!/bin/sh +set -eu +umask 022 + +[ $# -ge 4 ] || { + echo "usage: $0 []" >&2 + exit 2 +} + +NFPM_VERSION=2.47.0 + +binary=$1 +PKG_VERSION=$2 +target=$3 +out=$4 +package=${5:-soulseek-rs} + +case "$target" in + x86_64-*) PKG_ARCH=amd64 rpm_arch=x86_64 ;; + aarch64-*) PKG_ARCH=arm64 rpm_arch=aarch64 ;; + *) echo "no package architecture for $target" >&2; exit 1 ;; +esac + +case "$(uname -s)-$(uname -m)" in + Linux-x86_64) nfpm_asset=Linux_x86_64 nfpm_sha256=0660ca602b2d2d2ae4781a06c692b3eeb9d437ffea05b831d76e41f4a3188783 ;; + Linux-aarch64) nfpm_asset=Linux_arm64 nfpm_sha256=1c0f5f2999b9a974bfb04fdb0cc3306096de530ac5dbb25d739cc5f5219c919c ;; + *) echo "no pinned nfpm build for this machine" >&2; exit 1 ;; +esac + +root=$(cd "$(dirname "$0")/.." && pwd) +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +stage=$root/target/nfpm +rm -rf "$stage" +mkdir -p "$stage" "$out" +out=$(cd "$out" && pwd) + +cp "$binary" "$stage/soulseek-rs" +"$binary" completions print bash > "$stage/soulseek-rs.bash" +"$binary" completions print zsh > "$stage/_soulseek-rs" +"$binary" completions print fish > "$stage/soulseek-rs.fish" +"$binary" man > "$stage/soulseek-rs.1" +gzip -9n "$stage/soulseek-rs.1" + +archive=nfpm_${NFPM_VERSION}_$nfpm_asset.tar.gz +curl -fsSL -o "$work/$archive" \ + "https://github.com/goreleaser/nfpm/releases/download/v$NFPM_VERSION/$archive" +echo "$nfpm_sha256 $work/$archive" | sha256sum -c - >/dev/null +tar -xzf "$work/$archive" -C "$work" nfpm + +export PKG_ARCH PKG_VERSION +cd "$root" +"$work/nfpm" package --config packaging/nfpm.yaml --packager deb --target "$out/$package-$PKG_ARCH.deb" +"$work/nfpm" package --config packaging/nfpm.yaml --packager rpm --target "$out/$package-$rpm_arch.rpm"