name: Release
permissions:
contents: write
on:
workflow_call:
inputs:
tag:
required: true
type: string
secrets:
CERTIFICATES_P12:
required: true
CERTIFICATES_P12_PASS:
required: true
APPLE_API_KEY_P8:
required: true
APPLE_API_KEY_ID:
required: true
APPLE_API_ISSUER_ID:
required: true
env:
CARGO_TERM_COLOR: always
jobs:
upload-assets:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: refs/tags/${{ inputs.tag }}
persist-credentials: false
- name: Import codesign certs
if: startsWith(matrix.os, 'macos')
uses: apple-actions/import-codesign-certs@5142e029c445c10ffc7149d172e540235a065466 with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASS }}
- name: Install cross-compilation tools
uses: taiki-e/setup-cross-toolchain-action@12b7ad4acfa95a1476779d6c06699b96ec1691f8 with:
target: ${{ matrix.target }}
- name: Upload binary (macOS signed)
if: startsWith(matrix.os, 'macos')
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 with:
bin: communique
target: ${{ matrix.target }}
ref: refs/tags/${{ inputs.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
codesign: "Developer ID Application: Jeffrey Dickey (4993Y37DX6)"
codesign_prefix: dev.jdx.
codesign_options: runtime
- name: Notarize macOS binary
if: startsWith(matrix.os, 'macos')
shell: bash
env:
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
BINARIES: target/${{ matrix.target }}/release/communique
run: |
set -euo pipefail
if [ -z "$APPLE_API_KEY_P8" ] || [ -z "$APPLE_API_KEY_ID" ] || [ -z "$APPLE_API_ISSUER_ID" ]; then
echo "::error title=Notarization credentials missing::App Store Connect credentials are required for macOS releases."
exit 1
fi
# Outside the workspace, so no later step can sweep the key into an
# artifact, and removed even when notarization fails.
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
printf '%s' "$APPLE_API_KEY_P8" | base64 --decode > "$work/key.p8"
# notarytool takes a container, never a bare executable. Copies are
# fine: a Mach-O carries its signature inline, so the cdhash the
# ticket is keyed to is the one already shipped.
mkdir "$work/stage"
for binary in $BINARIES; do
cp "$binary" "$work/stage/"
done
# Apple rejects a binary without the hardened runtime or a secure
# timestamp. Naming a missing one here turns an opaque Invalid verdict
# into a diagnosis; Apple stays the authority on whether to accept.
for binary in $BINARIES; do
details="$(codesign --display --verbose=4 "$binary" 2>&1 || true)"
case "$details" in
*runtime*) ;;
*) echo "::warning title=No hardened runtime::$binary is signed without --options runtime, which Apple requires; expect an Invalid verdict below." ;;
esac
case "$details" in
*Timestamp=*) ;;
*) echo "::warning title=No secure timestamp::$binary carries no secure timestamp, which Apple requires; expect an Invalid verdict below." ;;
esac
done
ditto -c -k --norsrc --noextattr "$work/stage" "$work/submission.zip"
# `--wait` is not a gate on its own: it can return zero on an Invalid
# submission, so the reported status is what decides. plutil reads
# JSON and ships with macOS, which keeps this off jq.
result="$(xcrun notarytool submit "$work/submission.zip" \
--key "$work/key.p8" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER_ID" \
--output-format json \
--timeout 30m \
--wait)"
echo "$result"
status="$(printf '%s' "$result" | plutil -extract status raw -o - -)"
id="$(printf '%s' "$result" | plutil -extract id raw -o - -)"
if [ "$status" != "Accepted" ]; then
echo "::error::notarization returned $status for submission $id"
xcrun notarytool log "$id" \
--key "$work/key.p8" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER_ID" || true
exit 1
fi
echo "notarized: submission $id"
- name: Upload binary (non-macOS)
if: ${{ !startsWith(matrix.os, 'macos') }}
uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 with:
bin: communique
target: ${{ matrix.target }}
ref: refs/tags/${{ inputs.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
packslip:
name: Publish the packslip
needs: upload-assets
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Download the release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
REPO: ${{ github.repository }}
run: |
mkdir -p dist
gh release download "$TAG" --repo "$REPO" --dir dist --clobber \
--pattern 'communique-*.tar.gz' --pattern 'communique-*.zip'
ls -la dist
- name: Install completion syntax checkers
timeout-minutes: 5
run: |
if [[ -f /etc/apt/apt-mirrors.txt ]]; then
sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' /etc/apt/apt-mirrors.txt
fi
sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 update
sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 install -y zsh fish
# Hosted runners include PowerShell; Namespace images have its signed apt repository.
if ! command -v pwsh >/dev/null 2>&1; then
sudo apt-get -o Acquire::Retries=3 -o Acquire::http::Timeout=15 -o Acquire::https::Timeout=15 install -y powershell
fi
- name: Publish the CLI specification and completions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
REPO: ${{ github.repository }}
run: |
mkdir -p bin
tar -xzf dist/communique-x86_64-unknown-linux-gnu.tar.gz -C bin
bin/communique usage > communique.usage.kdl
for shell in bash zsh fish powershell; do
bin/communique completion "$shell" > communique.$shell
test -s communique.$shell
grep -Fq '__complete_word__' communique.$shell
bin/communique __complete_word__ --shell "$shell" --line 'communique ge' | grep -Fq -- 'generate'
done
bash -n communique.bash
zsh -n communique.zsh
fish --no-execute communique.fish
cat > completion-syntax-check.ps1 <<'POWERSHELL'
$ErrorActionPreference = 'Stop'
foreach ($script in Get-ChildItem -LiteralPath "." -Filter '*.powershell') {
$parseErrors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile($script.FullName, [ref]$null, [ref]$parseErrors)
if ($parseErrors.Count) { $parseErrors | Write-Error; exit 1 }
}
POWERSHELL
pwsh -NoProfile -NonInteractive -File completion-syntax-check.ps1
gh release upload "$TAG" communique.usage.kdl communique.bash communique.zsh communique.fish communique.powershell --repo "$REPO" --clobber
- uses: jdx/packslip@a0d434ad57571c62dbd0ab5095b4eff607a71fd3 env:
GH_REPO: ${{ github.repository }}
with:
tag: ${{ inputs.tag }}
artifacts: dist/communique-*.tar.gz dist/communique-*.zip
bin: communique
resources: |
cli-spec/usage=asset:communique.usage.kdl
completion/bash=asset:communique.bash
completion/zsh=asset:communique.zsh
completion/fish=asset:communique.fish
completion/powershell=asset:communique.powershell