name: Release
on:
push:
branches: [main]
env:
CARGO_TERM_COLOR: always
permissions:
contents: write
jobs:
check-version:
name: Check for version bump
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.check.outputs.should_release }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
fetch-tags: true
- name: Check if version tag exists
id: check
run: |
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if git tag -l "v$VERSION" | grep -q "v$VERSION"; then
echo "Tag v$VERSION already exists, skipping release"
echo "should_release=false" >> "$GITHUB_OUTPUT"
else
echo "Tag v$VERSION does not exist, proceeding with release"
echo "should_release=true" >> "$GITHUB_OUTPUT"
fi
ci:
name: CI checks
needs: check-version
if: needs.check-version.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: cargo check
run: cargo check --locked
- name: cargo test
run: cargo test --locked
- name: cargo clippy
run: cargo clippy --locked --tests -- -D warnings
- uses: taiki-e/install-action@cargo-deny
- name: cargo deny check licenses
run: cargo deny check licenses
- name: Generate distribution assets
run: cargo run --locked --example generate_assets
- name: Upload distribution assets
uses: actions/upload-artifact@v6
with:
name: dist-assets
path: target/assets/
retention-days: 1
build:
name: Build ${{ matrix.target }}
needs: ci
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- target: x86_64-apple-darwin
runner: macos-14
archive: jjpr-x86_64-apple-darwin.tar.gz
is_macos: true
- target: aarch64-apple-darwin
runner: macos-14
archive: jjpr-aarch64-apple-darwin.tar.gz
is_macos: true
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
archive: jjpr-x86_64-unknown-linux-gnu.tar.gz
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
archive: jjpr-aarch64-unknown-linux-gnu.tar.gz
- target: x86_64-pc-windows-msvc
runner: windows-latest
archive: jjpr-x86_64-pc-windows-msvc.zip
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross
if: matrix.target == 'aarch64-unknown-linux-gnu'
uses: taiki-e/install-action@cross
- name: Build binary
shell: bash
run: |
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
cross build --release --locked --target ${{ matrix.target }}
else
cargo build --release --locked --target ${{ matrix.target }}
fi
- name: Import Apple certificate
if: matrix.is_macos && env.APPLE_CERT != ''
env:
APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV"
echo "$APPLE_CERT" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$RUNNER_TEMP/certificate.p12" -P "$APPLE_CERT_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH"
rm "$RUNNER_TEMP/certificate.p12"
- name: Sign binary
if: matrix.is_macos && env.APPLE_CERT != ''
env:
APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
run: |
BINARY="target/${{ matrix.target }}/release/jjpr"
IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "Signing with identity: $IDENTITY"
codesign --force --options=runtime --sign "$IDENTITY" --timestamp "$BINARY"
codesign -v --verify --deep --strict "$BINARY"
- name: Notarize binary
if: matrix.is_macos && env.APPLE_CERT != ''
env:
APPLE_CERT: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
BINARY="target/${{ matrix.target }}/release/jjpr"
/usr/bin/ditto -c -k "$BINARY" "$RUNNER_TEMP/jjpr.zip"
xcrun notarytool submit "$RUNNER_TEMP/jjpr.zip" \
--apple-id "$APPLE_ID" \
--password "$APPLE_ID_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
rm "$RUNNER_TEMP/jjpr.zip"
- name: Download distribution assets
uses: actions/download-artifact@v7
with:
name: dist-assets
path: dist-assets
- name: Package binary (unix)
if: runner.os != 'Windows'
run: |
mkdir -p staging/completions
cp target/${{ matrix.target }}/release/jjpr staging/
cp dist-assets/jjpr.1 staging/
cp dist-assets/completions/* staging/completions/
cp README.md LICENSE-MIT LICENSE-APACHE staging/
cd staging
tar czf ../${{ matrix.archive }} jjpr jjpr.1 completions/ README.md LICENSE-MIT LICENSE-APACHE
- name: Package binary (windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Compress-Archive -Path "target/${{ matrix.target }}/release/jjpr.exe" -DestinationPath "${{ matrix.archive }}"
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.archive }}
path: ${{ matrix.archive }}
retention-days: 1
- name: Cleanup keychain
if: always() && matrix.is_macos && env.KEYCHAIN_PATH != ''
run: security delete-keychain "$KEYCHAIN_PATH"
release:
name: Create release and publish
needs: [check-version, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish || echo "::warning::Publish failed (version may already exist)"
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Download all artifacts
uses: actions/download-artifact@v7
with:
path: artifacts
merge-multiple: true
- name: Create git tag
run: |
git tag "v${{ needs.check-version.outputs.version }}"
git push origin "v${{ needs.check-version.outputs.version }}"
- name: Generate release notes
run: |
cargo install git-cliff
git cliff --latest --strip all > RELEASE_NOTES.md
- name: Generate checksums
run: |
cd artifacts
sha256sum *.tar.gz *.zip > SHA256SUMS.txt
cat SHA256SUMS.txt
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.check-version.outputs.version }}
name: v${{ needs.check-version.outputs.version }}
body_path: RELEASE_NOTES.md
files: |
artifacts/*.tar.gz
artifacts/*.zip
artifacts/SHA256SUMS.txt
- name: Trigger Homebrew tap update
if: env.TAP_TOKEN != ''
env:
TAP_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
uses: peter-evans/repository-dispatch@v4
with:
token: ${{ secrets.TAP_GITHUB_TOKEN }}
repository: michaeldhopkins/homebrew-tap
event-type: update-formula
client-payload: '{"formula": "jjpr", "repo": "michaeldhopkins/jjpr", "version": "${{ needs.check-version.outputs.version }}"}'