name: Release
on:
push:
tags: ["v*.*.*"]
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
verify:
name: Verify release gates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 - run: cargo fmt --all -- --check
- run: cargo test --all-features --locked
- run: cargo clippy --all-targets --all-features --locked -- -D warnings
build:
name: Build ${{ matrix.target }}
needs: verify
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset: devclean-linux-x86_64
- os: macos-latest
target: aarch64-apple-darwin
asset: devclean-macos-arm64
- os: macos-latest
target: x86_64-apple-darwin
asset: devclean-macos-x86_64
- os: windows-latest
target: x86_64-pc-windows-msvc
asset: devclean-windows-x86_64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 with:
key: ${{ matrix.target }}
- run: cargo build --release --locked --target ${{ matrix.target }}
- name: Package Unix archive
if: runner.os != 'Windows'
shell: bash
run: |
mkdir package
cp "target/${{ matrix.target }}/release/devclean" package/
cp README.md LICENSE package/
tar -C package -czf "${{ matrix.asset }}.tar.gz" .
- name: Package Windows archive
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Path package
Copy-Item "target/${{ matrix.target }}/release/devclean.exe" package/
Copy-Item README.md,LICENSE package/
Compress-Archive -Path package/* -DestinationPath "${{ matrix.asset }}.zip"
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a with:
subject-path: ${{ matrix.asset }}.*
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: ${{ matrix.asset }}
path: ${{ matrix.asset }}.*
if-no-files-found: error
macos-app:
name: Build macOS menu bar app
needs: [verify, build]
runs-on: macos-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: devclean-macos-*
path: cli-artifacts
- name: Create universal Rust helper
shell: bash
run: |
mkdir -p cli-arm64 cli-x86_64 target/universal-apple-darwin/release
tar -xzf cli-artifacts/devclean-macos-arm64/devclean-macos-arm64.tar.gz -C cli-arm64
tar -xzf cli-artifacts/devclean-macos-x86_64/devclean-macos-x86_64.tar.gz -C cli-x86_64
lipo -create cli-arm64/devclean cli-x86_64/devclean \
-output target/universal-apple-darwin/release/devclean
test "$(lipo -archs target/universal-apple-darwin/release/devclean)" = "x86_64 arm64" \
-o "$(lipo -archs target/universal-apple-darwin/release/devclean)" = "arm64 x86_64"
- name: Test Swift package
run: swift test --package-path apps/macos
- name: Import Developer ID certificate
env:
MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
shell: bash
run: |
test -n "$MACOS_CERTIFICATE_P12"
certificate_path="$RUNNER_TEMP/developer-id.p12"
keychain_path="$RUNNER_TEMP/devclean-signing.keychain-db"
keychain_password="$(openssl rand -hex 32)"
printf '%s' "$MACOS_CERTIFICATE_P12" | base64 --decode > "$certificate_path"
chmod 600 "$certificate_path"
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 "$certificate_path" -k "$keychain_path" \
-P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$keychain_password" "$keychain_path"
security list-keychains -d user -s "$keychain_path"
identity_hash="$(security find-identity -v -p codesigning "$keychain_path" | \
awk '/Developer ID Application: LE ANH TUAN \(VGQU7EVXZV\)/ {print $2; exit}')"
test -n "$identity_hash"
echo "CODE_SIGN_IDENTITY=$identity_hash" >> "$GITHUB_ENV"
echo "SIGNING_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
- name: Build universal app
env:
UNIVERSAL: "1"
HELPER_EXECUTABLE: ${{ github.workspace }}/target/universal-apple-darwin/release/devclean
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: apps/macos/scripts/build-app.sh
- name: Verify universal app
run: |
codesign --verify --deep --strict --verbose=2 dist/DevCleaner.app
test -f dist/DevCleaner.app/Contents/Resources/AppIcon.icns
lipo -archs dist/DevCleaner.app/Contents/MacOS/DevcleanMenuBar | grep arm64
lipo -archs dist/DevCleaner.app/Contents/MacOS/DevcleanMenuBar | grep x86_64
lipo -archs dist/DevCleaner.app/Contents/Helpers/devclean | grep arm64
lipo -archs dist/DevCleaner.app/Contents/Helpers/devclean | grep x86_64
codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'Authority=Developer ID Application'
codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'TeamIdentifier=VGQU7EVXZV'
codesign -dvvv dist/DevCleaner.app 2>&1 | grep 'Runtime Version='
- name: Notarize and staple app
env:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
shell: bash
run: |
test -n "$APPLE_API_KEY"
api_key_path="$RUNNER_TEMP/AuthKey.p8"
submission="$RUNNER_TEMP/devclean-notarization.zip"
result="$RUNNER_TEMP/notarization-result.json"
printf '%s' "$APPLE_API_KEY" > "$api_key_path"
chmod 600 "$api_key_path"
ditto -c -k --keepParent dist/DevCleaner.app "$submission"
xcrun notarytool submit "$submission" \
--key "$api_key_path" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER" \
--wait --output-format json > "$result"
jq -e '.status == "Accepted"' "$result"
xcrun stapler staple dist/DevCleaner.app
xcrun stapler validate dist/DevCleaner.app
codesign --verify --deep --strict --verbose=2 dist/DevCleaner.app
spctl --assess --type execute --verbose=4 dist/DevCleaner.app
rm -f "$api_key_path" "$submission" "$result"
- name: Package app bundle
run: ditto -c -k --sequesterRsrc --keepParent dist/DevCleaner.app devclean-menubar-macos-universal.zip
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a with:
subject-path: devclean-menubar-macos-universal.zip
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: devclean-menubar-macos-universal
path: devclean-menubar-macos-universal.zip
if-no-files-found: error
- name: Delete temporary signing keychain
if: always()
run: security delete-keychain "$SIGNING_KEYCHAIN" || true
publish:
name: Publish GitHub release
needs: [build, macos-app]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: dist
merge-multiple: true
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 with:
path: .
format: cyclonedx-json
output-file: dist/devclean-sbom.cdx.json
upload-artifact: false
- uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 with:
subject-path: dist/devclean-*.*
sbom-path: dist/devclean-sbom.cdx.json
- name: Package skill and checksums
shell: bash
run: |
tar -czf dist/dev-disk-cleaner-skill.tar.gz skills/dev-disk-cleaner
cd dist
sha256sum * > SHA256SUMS
- uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b with:
name: devclean ${{ github.ref_name }}
body_path: RELEASE_NOTES.md
make_latest: true
files: dist/*