From 6c6369b9dedb8f04f7d17a4cdeab7b4b4c414f29 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 28 Sep 2025 23:17:21 +0100 Subject: [PATCH] Rely on shared action for packaging --- .github/workflows/release.yml | 71 +++++++++++++++++----- crates/comenq/build.rs | 18 ++++++ crates/comenqd/build.rs | 18 ++++++ docs/automated-cross-platform-packaging.md | 32 +++++++--- docs/comenq-design.md | 21 ++++--- packaging/man/comenq.1 | 37 +++++++++++ packaging/man/comenqd.1 | 44 ++++++++++++++ test-support/src/lib.rs | 2 +- test-support/src/workflow.rs | 60 ++++++++++++------ tests/features/release.feature | 4 +- tests/steps/release_steps.rs | 8 +-- 11 files changed, 258 insertions(+), 57 deletions(-) create mode 100644 crates/comenq/build.rs create mode 100644 crates/comenqd/build.rs create mode 100644 packaging/man/comenq.1 create mode 100644 packaging/man/comenqd.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e57785..03727ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,33 +3,72 @@ name: Release on: push: tags: - # Match semantic version tags (e.g. v1.2.3, v10.11.12, v12.3.7-beta7) - 'v*.*.*' jobs: - goreleaser: + build-packages: + name: Build ${{ matrix.bin }} for ${{ matrix.target }} runs-on: ubuntu-latest + strategy: + matrix: + include: + - bin: comenq + target: x86_64-unknown-linux-gnu + arch: amd64 + - bin: comenqd + target: x86_64-unknown-linux-gnu + arch: amd64 + - bin: comenq + target: aarch64-unknown-linux-gnu + arch: arm64 + - bin: comenqd + target: aarch64-unknown-linux-gnu + arch: arm64 steps: - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable + - name: Prepare release version + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + echo "RELEASE_VERSION=${version}" >> "$GITHUB_ENV" + - name: Clean dist directory + run: rm -rf dist + - name: Build and package ${{ matrix.bin }} + uses: leynos/shared-actions/.github/actions/rust-build-release@7bc9b6c15964ef98733aa647b76d402146284ba3 with: - toolchain: stable - targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu - cache: cargo - - name: Set up Go - uses: actions/setup-go@v6 + target: ${{ matrix.target }} + bin-name: ${{ matrix.bin }} + version: ${{ env.RELEASE_VERSION }} + formats: deb,rpm + - name: Upload artefacts + uses: actions/upload-artifact@v4 with: - go-version: '1.21' - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + name: ${{ matrix.bin }}-${{ matrix.arch }} + path: | + dist/**/*.deb + dist/**/*.rpm + dist/nfpm.yaml + dist/.man/** + if-no-files-found: error + release: + name: Publish GitHub release + runs-on: ubuntu-latest + needs: build-packages + steps: + - name: Download artefacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + - name: Create draft release + uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 with: - distribution: goreleaser - # Use a fixed version to ensure reproducibility - version: v1.24.0 - args: release --clean + tag_name: ${{ github.ref_name }} + draft: true + files: | + release-artifacts/**/*.deb + release-artifacts/**/*.rpm env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/crates/comenq/build.rs b/crates/comenq/build.rs new file mode 100644 index 0000000..0f5df73 --- /dev/null +++ b/crates/comenq/build.rs @@ -0,0 +1,18 @@ +//! Stages the packaged client man page for installation. +use std::{env, fs, path::PathBuf}; + +fn main() { + if let Err(error) = copy_man_page() { + panic!("failed to stage man page: {error}"); + } +} + +fn copy_man_page() -> std::io::Result<()> { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + let source = manifest_dir.join("../../packaging/man/comenq.1"); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); + let dest = out_dir.join("comenq.1"); + fs::copy(&source, &dest)?; + println!("cargo:rerun-if-changed={}", source.display()); + Ok(()) +} diff --git a/crates/comenqd/build.rs b/crates/comenqd/build.rs new file mode 100644 index 0000000..b00489d --- /dev/null +++ b/crates/comenqd/build.rs @@ -0,0 +1,18 @@ +//! Stages the packaged daemon man page for installation. +use std::{env, fs, path::PathBuf}; + +fn main() { + if let Err(error) = copy_man_page() { + panic!("failed to stage man page: {error}"); + } +} + +fn copy_man_page() -> std::io::Result<()> { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + let source = manifest_dir.join("../../packaging/man/comenqd.1"); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); + let dest = out_dir.join("comenqd.1"); + fs::copy(&source, &dest)?; + println!("cargo:rerun-if-changed={}", source.display()); + Ok(()) +} diff --git a/docs/automated-cross-platform-packaging.md b/docs/automated-cross-platform-packaging.md index 815aa1a..8bf244c 100644 --- a/docs/automated-cross-platform-packaging.md +++ b/docs/automated-cross-platform-packaging.md @@ -4,14 +4,30 @@ This guide provides a step-by-step process for configuring a GitHub Actions workflow to automatically build and package the `comenq` client and `comenqd` -daemon for Linux (Fedora, Ubuntu) and macOS. macOS packaging is currently on -hold, so the workflow focuses on Linux targets only. GoReleaser manages the -entire process, from building the Rust binaries to creating platform-native -packages (`.rpm`, `.deb`) and a Homebrew formula. - -The core of this process involves creating a `.goreleaser.yaml` file that -declaratively defines the build, packaging, and release steps. This file will -be used by a GitHub Actions workflow that triggers on new git tags. +daemon for Linux (Fedora, Ubuntu). macOS packaging remains on hold, so the +workflow focuses on Linux targets only. The current implementation replaces the +earlier GoReleaser flow with composite actions hosted in +`leynos/shared-actions`. These actions orchestrate the Rust build, stage the +man pages generated by the workspace build scripts, and delegate all packaging +to the shared `linux-packages` helper. The helper emits `.rpm` and `.deb` +artefacts for each binary without invoking `nfpm` directly in the workflow. + +The workflow matrix iterates over both binaries (`comenq` and `comenqd`) and +the supported Linux targets (`x86_64-unknown-linux-gnu` and +`aarch64-unknown-linux-gnu`). For each combination the `rust-build-release` +action compiles the workspace with the correct target toolchain and stages the +matching man page. The downstream `linux-packages` composite action, executed +by `rust-build-release`, generates the `nfpm` manifest and packages the staged +artefacts in one pass. This keeps the workflow lean—only the shared action ever +executes `nfpm`—and ensures the generated `.deb` and `.rpm` files are ready to +upload. Finally, `softprops/action-gh-release` publishes the generated packages +to a draft GitHub Release, preserving the previous workflow's release review +gate. + +The remainder of this document captures the original GoReleaser-oriented plan +for historical context. The high-level packaging requirements remain relevant, +but the automation now lives entirely in `.github/workflows/release.yml` via +the shared composite actions described above. ### Part 1: Packaging for Fedora and Ubuntu with systemd diff --git a/docs/comenq-design.md b/docs/comenq-design.md index cc2f993..2cd2f89 100644 --- a/docs/comenq-design.md +++ b/docs/comenq-design.md @@ -800,15 +800,18 @@ service. ### 4.4. Packaging and Release Workflow -To simplify installation, the project uses GoReleaser. The declarative -`.goreleaser.yaml` builds both binaries via the `goreleaser-rust` plugin, -eliminating manual pre-build hooks. The `nfpms` section produces signed `.deb` -and `.rpm` packages for Fedora and Ubuntu, embedding the hardened `systemd` -service unit and lifecycle scripts that create the `comenq` user. This keeps -packaging logic version controlled and repeatable. A GitHub Actions workflow -triggers on version tags to run GoReleaser. It builds Linux packages and -uploads them to a draft release. Mac support is currently deferred, so the -workflow targets Linux only. +To simplify installation, the project now relies on the composite actions +published in `leynos/shared-actions`. The release workflow iterates over the +`comenq` client and `comenqd` daemon for both the x86_64 and aarch64 GNU/Linux +targets. `rust-build-release` provisions the correct Rust toolchain, compiles +the workspace in release mode, and stages the man pages that each crate's build +script copies from `packaging/man`. Packaging responsibility sits entirely with +the shared `linux-packages` helper invoked by `rust-build-release`; it +generates the transient `nfpm` manifest and emits `.deb` and `.rpm` artefacts +for every matrix entry. The workflow uploads those artefacts to a draft GitHub +Release via `softprops/action-gh-release`, preserving the manual review gate +that existed in the GoReleaser-based flow. macOS support remains deferred, so +the workflow targets Linux only. ## Section 5: Complete Source Code and Project Manifest diff --git a/packaging/man/comenq.1 b/packaging/man/comenq.1 new file mode 100644 index 0000000..1e63947 --- /dev/null +++ b/packaging/man/comenq.1 @@ -0,0 +1,37 @@ +.TH COMENQ 1 "May 2024" "comenq" "User Commands" +.SH NAME +comenq \- enqueue a GitHub pull request comment for the comenqd daemon +.SH SYNOPSIS +.B comenq +.I owner/repo +.I pr-number +.I comment +[ +.B --socket +.I path +] +.SH DESCRIPTION +The +.B comenq +command enqueues a comment request for the +.BR comenqd (8) +daemon. It takes the target repository in +.I owner/repo +format, the pull request number, and the comment body to submit. +.PP +Use +.B --socket +to override the Unix Domain Socket path used to contact the daemon. The +socket defaults to +.I /run/comenq/comenq.sock +which matches the packaged systemd service configuration. +.SH EXAMPLES +Send a comment to pull request 42 of example/repo: +.PP +.RS +.nf +comenq example/repo 42 "Queued for review" +.fi +.RE +.SH SEE ALSO +.BR comenqd (8) diff --git a/packaging/man/comenqd.1 b/packaging/man/comenqd.1 new file mode 100644 index 0000000..3d3193c --- /dev/null +++ b/packaging/man/comenqd.1 @@ -0,0 +1,44 @@ +.TH COMENQD 8 "May 2024" "comenqd" "System Administration" +.SH NAME +comenqd \- daemon that posts queued GitHub pull request comments +.SH SYNOPSIS +.B comenqd +[ +.B --config +.I file +] +[ +.B --github-token +.I token +] +[ +.B --socket-path +.I path +] +[ +.B --queue-path +.I path +] +.SH DESCRIPTION +The +.B comenqd +daemon consumes comment requests queued by the +.BR comenq (1) +CLI and posts them to GitHub. Configuration is loaded from +.I /etc/comenqd/config.toml +and may be overridden through command line options or +environment variables prefixed with +.BR COMENQD_. +.PP +The daemon is designed to run as a system service using the packaged +systemd unit. When launched manually it logs its effective socket and +queue locations to the terminal. +.SH FILES +.TP +.I /etc/comenqd/config.toml +Default configuration file. +.TP +.I /var/lib/comenq/queue +On-disk queue storage used for persistence across restarts. +.SH SEE ALSO +.BR comenq (1) diff --git a/test-support/src/lib.rs b/test-support/src/lib.rs index 247fffd..c7b0af9 100644 --- a/test-support/src/lib.rs +++ b/test-support/src/lib.rs @@ -4,7 +4,7 @@ pub mod daemon; pub mod env_guard; pub mod util; mod workflow; -pub use workflow::uses_goreleaser; +pub use workflow::uses_shared_release_actions; // Re-exports from daemon module (added in main) pub use daemon::{TestConfig, octocrab_for, temp_config}; diff --git a/test-support/src/workflow.rs b/test-support/src/workflow.rs index 5cd66a0..666147e 100644 --- a/test-support/src/workflow.rs +++ b/test-support/src/workflow.rs @@ -2,12 +2,13 @@ use serde_yaml::Value; -/// Return `true` if the workflow steps include the `GoReleaser` action. +/// Return `true` when the release workflow uses the shared composite actions to +/// build binaries and publish packages. /// /// # Errors /// /// Returns an error if the YAML cannot be parsed. -pub fn uses_goreleaser(yaml: &str) -> Result { +pub fn uses_shared_release_actions(yaml: &str) -> Result { let doc: Value = serde_yaml::from_str(yaml)?; let Some(jobs) = doc.get("jobs") else { return Ok(false); @@ -15,6 +16,9 @@ pub fn uses_goreleaser(yaml: &str) -> Result { let Some(map) = jobs.as_mapping() else { return Ok(false); }; + + let mut saw_rust_builder = false; + let mut saw_release_publisher = false; for job in map.values() { let Some(steps) = job.get("steps") else { continue; @@ -23,38 +27,60 @@ pub fn uses_goreleaser(yaml: &str) -> Result { continue; }; for step in arr { - if step - .get("uses") - .and_then(|u| u.as_str()) - .is_some_and(|s| s.starts_with("goreleaser/goreleaser-action")) - { - return Ok(true); + if let Some(uses) = step.get("uses").and_then(Value::as_str) { + if uses + == "leynos/shared-actions/.github/actions/rust-build-release@7bc9b6c15964ef98733aa647b76d402146284ba3" + { + saw_rust_builder = true; + } + if uses.starts_with("softprops/action-gh-release@") { + saw_release_publisher = true; + } } } } - Ok(false) + + Ok(saw_rust_builder && saw_release_publisher) } #[cfg(test)] mod tests { - use super::uses_goreleaser; + use super::uses_shared_release_actions; + + #[test] + #[expect(clippy::expect_used, reason = "simplify test output")] + fn detects_shared_actions() { + let yaml = r" + jobs: + release: + steps: + - uses: leynos/shared-actions/.github/actions/rust-build-release@7bc9b6c15964ef98733aa647b76d402146284ba3 + - uses: softprops/action-gh-release@v2 + "; + assert!(uses_shared_release_actions(yaml).expect("parse")); + } #[test] #[expect(clippy::expect_used, reason = "simplify test output")] - fn detects_goreleaser() { + fn missing_builder_fails() { let yaml = r" jobs: - goreleaser: + release: steps: - - uses: goreleaser/goreleaser-action@v5 + - uses: softprops/action-gh-release@v2 "; - assert!(uses_goreleaser(yaml).expect("parse")); + assert!(!uses_shared_release_actions(yaml).expect("parse")); } #[test] #[expect(clippy::expect_used, reason = "simplify test output")] - fn missing_goreleaser() { - let yaml = "jobs: {}"; - assert!(!uses_goreleaser(yaml).expect("parse")); + fn missing_publisher_fails() { + let yaml = r" + jobs: + release: + steps: + - uses: leynos/shared-actions/.github/actions/rust-build-release@7bc9b6c15964ef98733aa647b76d402146284ba3 + "; + assert!(!uses_shared_release_actions(yaml).expect("parse")); } } diff --git a/tests/features/release.feature b/tests/features/release.feature index b65566a..d3a435a 100644 --- a/tests/features/release.feature +++ b/tests/features/release.feature @@ -1,9 +1,9 @@ Feature: Release workflow - Scenario: goreleaser step present + Scenario: shared release actions present Given the release workflow file When it is parsed as YAML - Then the workflow uses goreleaser + Then the workflow uses the shared release actions Scenario: triggers on version tags Given the release workflow file diff --git a/tests/steps/release_steps.rs b/tests/steps/release_steps.rs index f19e2c6..84e4779 100644 --- a/tests/steps/release_steps.rs +++ b/tests/steps/release_steps.rs @@ -4,7 +4,7 @@ use cucumber::{World, given, then, when}; use regex::Regex; use serde_yaml::Value; use std::fs; -use test_support::uses_goreleaser as workflow_uses_goreleaser; +use test_support::uses_shared_release_actions as workflow_uses_shared_actions; #[derive(Debug, Default, World)] pub struct ReleaseWorld { @@ -26,11 +26,11 @@ fn parse_yaml(world: &mut ReleaseWorld) { world.yaml = Some(serde_yaml::from_str(text).expect("parse yaml")); } -#[then("the workflow uses goreleaser")] +#[then("the workflow uses the shared release actions")] #[expect(clippy::expect_used, reason = "simplify test failure output")] -fn assert_uses_goreleaser(world: &mut ReleaseWorld) { +fn assert_uses_shared_actions(world: &mut ReleaseWorld) { let content = world.content.as_ref().expect("file still loaded"); - assert!(workflow_uses_goreleaser(content).expect("parse")); + assert!(workflow_uses_shared_actions(content).expect("parse")); } #[then("the workflow triggers on tags")]