diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml new file mode 100644 index 0000000..ac6d127 --- /dev/null +++ b/.github/workflows/release-dry-run.yml @@ -0,0 +1,12 @@ +name: Release Dry Run + +'on': + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + release: + uses: ./.github/workflows/release.yml + with: + dry-run: true + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c26c4eb..968720d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,71 @@ name: Release -on: +'on': push: tags: - 'v*.*.*' + workflow_call: + inputs: + publish: + description: >- + Whether the workflow should publish artefacts to the GitHub release + associated with the tag. Defaults to `false` when invoked as a + reusable workflow. + required: false + type: boolean + default: false + dry-run: + description: >- + When `true`, build artefacts without uploading them to GitHub + releases or workflow artefacts. + required: false + type: boolean + default: false + outputs: + version: + description: Package version + value: ${{ jobs.metadata.outputs.version }} + should_publish: + description: Whether to publish + value: ${{ jobs.metadata.outputs.should_publish }} + dry_run: + description: Dry run mode + value: ${{ jobs.metadata.outputs.dry_run }} + should_upload_workflow_artifacts: + description: Upload workflow artifacts + value: ${{ jobs.metadata.outputs.should_upload_workflow_artifacts }} + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false jobs: + metadata: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.ensure_version.outputs.crate-version }} + should_publish: ${{ steps.release_modes.outputs.should-publish }} + dry_run: ${{ steps.release_modes.outputs.dry-run }} + should_upload_workflow_artifacts: ${{ steps.release_modes.outputs.should-upload-workflow-artifacts }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Determine release modes + id: release_modes + uses: leynos/shared-actions/.github/actions/determine-release-modes@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 + with: + dry-run: ${{ inputs.dry-run }} + publish: ${{ inputs.publish }} + - name: Read package version + id: ensure_version + uses: leynos/shared-actions/.github/actions/ensure-cargo-version@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 + with: + check-tag: ${{ fromJSON(steps.release_modes.outputs.should-publish) }} + build-packages: name: Build ${{ matrix.bin }} for ${{ matrix.target }} + needs: metadata runs-on: ubuntu-latest env: PACKAGE_DEB_DEPENDS: ${{ matrix.bin == 'comenqd' && 'bash, systemd' || '' }} @@ -32,32 +90,27 @@ jobs: uses: actions/checkout@v5 with: fetch-depth: 0 - - 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 ${{ matrix.bin }} - uses: leynos/shared-actions/.github/actions/rust-build-release@1479e2ffbbf1053bb0205357dfe965299b7493ed + uses: leynos/shared-actions/.github/actions/rust-build-release@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 with: target: ${{ matrix.target }} bin-name: ${{ matrix.bin }} - version: ${{ env.RELEASE_VERSION }} - formats: deb,rpm + manifest-path: crates/${{ matrix.bin }}/Cargo.toml - name: Package ${{ matrix.bin }} - uses: leynos/shared-actions/.github/actions/linux-packages@1479e2ffbbf1053bb0205357dfe965299b7493ed + uses: leynos/shared-actions/.github/actions/linux-packages@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 with: bin-name: ${{ matrix.bin }} package-name: ${{ matrix.bin }} target: ${{ matrix.target }} - version: ${{ env.RELEASE_VERSION }} + version: ${{ needs.metadata.outputs.version }} formats: deb,rpm man-paths: dist/${{ matrix.bin }}_linux_${{ matrix.arch }}/${{ matrix.bin }}.1 deb-depends: ${{ env.PACKAGE_DEB_DEPENDS }} rpm-depends: ${{ env.PACKAGE_RPM_DEPENDS }} - name: Upload artefacts + if: fromJSON(needs.metadata.outputs.should_upload_workflow_artifacts) || fromJSON(needs.metadata.outputs.should_publish) uses: actions/upload-artifact@v4 with: name: ${{ matrix.bin }}-${{ matrix.arch }} @@ -67,22 +120,72 @@ jobs: dist/nfpm.yaml dist/.man/** if-no-files-found: error + release: name: Publish GitHub release + if: fromJSON(needs.metadata.outputs.should_publish) runs-on: ubuntu-latest - needs: build-packages + permissions: + contents: write + needs: [metadata, build-packages] steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Ensure release exists (draft) + shell: bash + run: | + set -euo pipefail + gh release view "${{ github.ref_name }}" >/dev/null 2>&1 || \ + gh release create "${{ github.ref_name }}" \ + --draft \ + --verify-tag \ + --notes "Automated release for ${{ github.ref_name }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Download artefacts uses: actions/download-artifact@v4 with: - path: release-artifacts - - name: Create draft release - uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 + path: dist + - id: upload_comenq + name: Upload comenq artefacts + uses: leynos/shared-actions/.github/actions/upload-release-assets@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 with: - tag_name: ${{ github.ref_name }} - draft: true - files: | - release-artifacts/**/*.deb - release-artifacts/**/*.rpm + release-tag: ${{ github.ref_name }} + bin-name: comenq + dist-dir: dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - id: upload_comenqd + name: Upload comenqd artefacts + uses: leynos/shared-actions/.github/actions/upload-release-assets@cb06757ebba47bb018ac0ade84fa5dc9ffb95020 + with: + release-tag: ${{ github.ref_name }} + bin-name: comenqd + dist-dir: dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check upload errors + if: steps.upload_comenq.outputs.upload-error == 'true' || steps.upload_comenqd.outputs.upload-error == 'true' + env: + UPLOAD_ERROR_COMENQ: ${{ steps.upload_comenq.outputs.upload-error }} + UPLOAD_ERROR_COMENQD: ${{ steps.upload_comenqd.outputs.upload-error }} + ERROR_MSG_COMENQ: ${{ steps.upload_comenq.outputs.error-message }} + ERROR_MSG_COMENQD: ${{ steps.upload_comenqd.outputs.error-message }} + run: | + has_error=false + if [ "$UPLOAD_ERROR_COMENQ" = "true" ]; then + echo "Error uploading comenq release assets:" + printf '%s\n' "$ERROR_MSG_COMENQ" + has_error=true + fi + if [ "$UPLOAD_ERROR_COMENQD" = "true" ]; then + echo "Error uploading comenqd release assets:" + printf '%s\n' "$ERROR_MSG_COMENQD" + has_error=true + fi + if [ "$has_error" = "true" ]; then + exit 1 + fi diff --git a/Cargo.lock b/Cargo.lock index 3109041..3a8ded3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2526,6 +2526,7 @@ name = "test-support" version = "0.1.0" dependencies = [ "octocrab", + "rstest", "serde", "serde_yaml", "serial_test", diff --git a/crates/comenq/Cargo.toml b/crates/comenq/Cargo.toml index 4503182..5dd25f5 100644 --- a/crates/comenq/Cargo.toml +++ b/crates/comenq/Cargo.toml @@ -6,6 +6,10 @@ edition = "2024" [lib] path = "src/lib.rs" +[[bin]] +name = "comenq" +path = "src/main.rs" + [dependencies] tokio = { workspace = true } clap = { workspace = true } diff --git a/crates/comenqd/Cargo.toml b/crates/comenqd/Cargo.toml index 4ff565b..5138ed5 100644 --- a/crates/comenqd/Cargo.toml +++ b/crates/comenqd/Cargo.toml @@ -3,6 +3,12 @@ name = "comenqd" version = "0.1.0" edition = "2024" +[lib] +path = "src/lib.rs" + +[[bin]] +name = "comenqd" +path = "src/main.rs" [dependencies] tokio = { workspace = true } diff --git a/crates/comenqd/src/util.rs b/crates/comenqd/src/util.rs index 9b2c039..26d29f4 100644 --- a/crates/comenqd/src/util.rs +++ b/crates/comenqd/src/util.rs @@ -2,11 +2,13 @@ //! //! Provides helpers used across production code and tests. +#[cfg(any(test, feature = "test-support"))] use std::ffi::OsStr; /// Names of files storing queue metadata. /// /// Extend this list when new metadata files are introduced. +#[cfg(any(test, feature = "test-support"))] pub(crate) const METADATA_FILE_NAMES: [&str; 3] = ["version", "recv.lock", "send.lock"]; /// Returns whether a file name represents queue metadata. @@ -19,6 +21,7 @@ pub(crate) const METADATA_FILE_NAMES: [&str; 3] = ["version", "recv.lock", "send /// assert!(is_metadata_file(OsStr::new("version"))); /// assert!(!is_metadata_file(OsStr::new("0001"))); /// ``` +#[cfg(any(test, feature = "test-support"))] pub fn is_metadata_file(name: impl AsRef) -> bool { let name = name.as_ref(); METADATA_FILE_NAMES.iter().any(|m| OsStr::new(m) == name) diff --git a/crates/comenqd/src/worker.rs b/crates/comenqd/src/worker.rs index 801d6ed..259de41 100644 --- a/crates/comenqd/src/worker.rs +++ b/crates/comenqd/src/worker.rs @@ -70,10 +70,6 @@ pub struct WorkerHooks { /// Signalled when the queue is empty and the worker is idle. /// /// Only one waiter is supported; additional waiters will not be notified. - #[cfg_attr( - not(any(test, feature = "test-support")), - expect(dead_code, reason = "test hook only used in test/test-support builds") - )] pub drained: Option>, } diff --git a/test-support/Cargo.toml b/test-support/Cargo.toml index 26f34a9..d5e2b7f 100644 --- a/test-support/Cargo.toml +++ b/test-support/Cargo.toml @@ -13,4 +13,5 @@ serde = { workspace = true } tracing-subscriber = { workspace = true } [dev-dependencies] +rstest = { workspace = true } serial_test = "^2" diff --git a/test-support/src/workflow.rs b/test-support/src/workflow.rs index b044ece..a363107 100644 --- a/test-support/src/workflow.rs +++ b/test-support/src/workflow.rs @@ -5,7 +5,7 @@ use serde_yaml::Value; // Provide the shared-actions commit hash as a literal so concat! can build constants without runtime formatting. macro_rules! shared_actions_commit_literal { () => { - "1479e2ffbbf1053bb0205357dfe965299b7493ed" + "cb06757ebba47bb018ac0ade84fa5dc9ffb95020" }; } @@ -18,6 +18,10 @@ const EXPECTED_SHARED_ACTIONS_COMMIT: &str = shared_actions_commit_literal!(); /// The prefix for the shared release build composite action identifier. const RUST_BUILD_RELEASE_PREFIX: &str = "leynos/shared-actions/.github/actions/rust-build-release@"; +/// The prefix for the shared release asset upload composite action identifier. +const UPLOAD_RELEASE_ASSETS_PREFIX: &str = + "leynos/shared-actions/.github/actions/upload-release-assets@"; + /// The release builder action reference expected by tests, built at compile time to avoid allocations. #[cfg(test)] const EXPECTED_RUST_BUILDER: &str = concat!( @@ -25,6 +29,13 @@ const EXPECTED_RUST_BUILDER: &str = concat!( shared_actions_commit_literal!(), ); +/// The release publisher action reference expected by tests, built at compile time to avoid allocations. +#[cfg(test)] +const EXPECTED_UPLOAD_RELEASE_ASSETS: &str = concat!( + "leynos/shared-actions/.github/actions/upload-release-assets@", + shared_actions_commit_literal!(), +); + /// Return `true` when the release workflow uses the shared composite actions to /// build binaries and publish packages. /// @@ -57,7 +68,10 @@ pub fn uses_shared_release_actions(yaml: &str) -> Result Result