Skip to content

Merge branch 'dev'

Merge branch 'dev' #16

name: 🍏 🏁 macOS and Windows Build, Bundle, and Publish
on:
push:
branches:
- main
- 'cicd/**'
jobs:
build_macos:
runs-on: macos-latest
outputs:
zip_filename: ${{ steps.create_zip_filename.outputs.zip_filename }}
steps:
- name: Get repository name
id: get_repo_name
run: echo "repo_name=$(basename $GITHUB_REPOSITORY)" >> "$GITHUB_OUTPUT"
- name: Checkout code
uses: actions/checkout@v4
with:
# Fetch all history for all tags and branches instead of `1`, which fetches only the current branch.
fetch-depth: 0
# Include icon images.
lfs: true
- name: Install Rust toolchain with M1 chip support
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: aarch64-apple-darwin, x86_64-apple-darwin
- name: Set up Rust Build Caching
uses: Swatinem/rust-cache@v2
# Build binaries in parallel to speed up build times.
- name: Build x86_64 binary and Apple Silicon binaries
run: cargo build --release --target x86_64-apple-darwin --target aarch64-apple-darwin
# Cargo Bundle can't create universal binaries, so make one with `lipo` and put it where Cargo Bundle expects to find it (`target/release/`).
- name: Create a universal binary from the x86_64 and ARM64 binaries and put it in `target/release/`
run: lipo -create -output target/release/${{ steps.get_repo_name.outputs.repo_name }} -arch x86_64 target/x86_64-apple-darwin/release/${{ steps.get_repo_name.outputs.repo_name }} -arch arm64 target/aarch64-apple-darwin/release/${{ steps.get_repo_name.outputs.repo_name }}
- name: Install `cargo-bundle` for creating `.app` directory structure and `.plist` with `cargo bundle`
uses: baptiste0928/cargo-install@v2
with:
crate: cargo-bundle
# Future: Cargo Bundle will build a universal binary and bundle it into an `*.app`.
- name: Create `*.app` directory structure and `.plist` file
env:
# Skip building binaries because they've already been built and combined.
CARGO_BUNDLE_SKIP_BUILD: "true"
run: cargo bundle --release --format osx
# Move the `*.app` directory to the top level of the repo so it's easier to access for code signing and notarization.
- name: Move bundled `*.app` directory to top level
run: mv target/release/bundle/osx/${{ steps.get_repo_name.outputs.repo_name }}.app .
- name: Codesign app bundle
env:
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
# Convert base64-encoded certificate back to .p12 file.
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
# Create a new keychain so no UI dialogs are generated.
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
# Codesign the app bundle with hardened runtime so it passes notarization.
/usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime ${{ steps.get_repo_name.outputs.repo_name }}.app -v
- name: "Notarize app bundle"
env:
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
# Prevent UI password dialog by storing notarization credentials.
echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
# We can't notarize an app bundle directly, but we need to compress it as an archive.
# Compress app bundle to zip file for notarization (because we can't notarize app bundles directly).
echo "Creating temporary notarization archive"
ditto -c -k --keepParent "${{ steps.get_repo_name.outputs.repo_name }}.app" "notarization.zip"
# Send the notarization request to the Apple's Notarization service.
echo "Notarizing compressed app bundle"
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
# "Attach the staple" to executable for offline MacOS validation.
echo "Attach staple"
xcrun stapler staple "${{ steps.get_repo_name.outputs.repo_name }}.app"
# Get the current SemVer from Cargo.toml. Ex. 2.0.3 would make v2.0.3 in this step.
- name: Get current SemVer version from Cargo.toml
id: get_current_semver
run: echo "semver=v$(cargo metadata --format-version 1 | jq -r '.packages | .[] | select(.name=="${{ steps.get_repo_name.outputs.repo_name }}") | .version')" >> "$GITHUB_OUTPUT"
- name: Create a filename for the macOS binary's zip file
id: create_zip_filename
run: echo "zip_filename=${{ steps.get_repo_name.outputs.repo_name }}_${{ steps.get_current_semver.outputs.semver }}_macos_universal.zip" >> "$GITHUB_OUTPUT"
- name: Compress *.app directory to *.zip, preserving resource fork and Finder information
run: ditto -c -k --sequesterRsrc --keepParent ${{ steps.get_repo_name.outputs.repo_name }}.app ${{ steps.create_zip_filename.outputs.zip_filename }}
- name: Create job summary about macOS binary creation
run: echo "## Created universal macOS binary" >> $GITHUB_STEP_SUMMARY
- name: Upload macOS executable for release
uses: actions/upload-artifact@v4
with:
# Give the artifact the same name as the zip file, otherwise it'll default to "artifact" and fail b/c already exists.
name: ${{ steps.create_zip_filename.outputs.zip_filename }}
path: ${{ steps.create_zip_filename.outputs.zip_filename }}
# Fail the Action if no zip was found.
if-no-files-found: error
build_windows:
runs-on: ubuntu-latest
outputs:
zip_filename: ${{ steps.create_zip_filename.outputs.zip_filename }}
steps:
- name: Get repository name
id: get_repo_name
run: echo "repo_name=$(basename $GITHUB_REPOSITORY)" >> "$GITHUB_OUTPUT"
- name: Checkout code
uses: actions/checkout@v4
with:
# Fetch all history for all tags and branches instead of `1`, which fetches only the current branch.
fetch-depth: 0
# Include icon images.
lfs: true
- name: Install Rust toolchain with M1 chip support
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: x86_64-pc-windows-gnu
- name: Install cross-compilation tools for building Windows executables on Ubuntu
run: |
sudo apt-get update
sudo apt-get install mingw-w64
- name: Set up rust build caching
uses: Swatinem/rust-cache@v2
- name: Build x86_64 Windows binary
run: cargo build --release --target x86_64-pc-windows-gnu
# Move the `*.exe` to the top level of the repo so it's easier to access.
- name: Move bundled `*.app` directory to top level
run: mv target/x86_64-pc-windows-gnu/release/${{ steps.get_repo_name.outputs.repo_name }}.exe .
- name: Get current SemVer version from Cargo.toml
id: get_current_semver
run: echo "semver=v$(cargo metadata --format-version 1 | jq -r '.packages | .[] | select(.name=="${{ steps.get_repo_name.outputs.repo_name }}") | .version')" >> "$GITHUB_OUTPUT"
- name: Create a filename for the Windows executable's zip file
id: create_zip_filename
run: echo "zip_filename=${{ steps.get_repo_name.outputs.repo_name }}_${{ steps.get_current_semver.outputs.semver }}_windows_gnu_x86_64.zip" >> "$GITHUB_OUTPUT"
- name: Zip the Windows executable
id: zip_executable
run: zip -j ${{ steps.create_zip_filename.outputs.zip_filename }} ${{ steps.get_repo_name.outputs.repo_name }}.exe
- name: Create job summary about executable creation
run: echo "## Created Windows executable" >> $GITHUB_STEP_SUMMARY
- name: Upload Windows executable for release
uses: actions/upload-artifact@v4
with:
# Give the artifact the same name as the zip file, otherwise it'll default to "artifact" and fail b/c already exists.
name: ${{ steps.create_zip_filename.outputs.zip_filename }}
path: ${{ steps.create_zip_filename.outputs.zip_filename }}
# Fail the Action if no zip was found.
if-no-files-found: error
publish_release:
needs: [build_macos, build_windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# We still need to check out the code for this job so we can get the SemVer.
- name: Checkout code
uses: actions/checkout@v4
with:
# Fetch all history for all tags and branches instead of `1`, which fetches only the current branch.
fetch-depth: 0
- name: Get repository name
id: get_repo_name
run: echo "repo_name=$(basename $GITHUB_REPOSITORY)" >> "$GITHUB_OUTPUT"
- name: Get current SemVer version from Cargo.toml
id: get_current_semver
run: echo "semver=v$(cargo metadata --format-version 1 | jq -r '.packages | .[] | select(.name=="${{ steps.get_repo_name.outputs.repo_name }}") | .version')" >> "$GITHUB_OUTPUT"
# Download all artifacts for this run.
- name: download artifacts
uses: actions/download-artifact@v4
with:
# Put artifacts in root instead of giving them their own directories.
merge-multiple: true
- name: Publish a new release
id: publish_release
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.get_current_semver.outputs.semver }}
# Gate release publishing to only happen on main branch. Otherwise, releasing will fail due to duplicate release names.
draft: ${{ github.ref == 'refs/heads/main' && 'false' || 'true' }}
commit: main
# Expect the macOS and windows zips to be available.
artifacts: "${{ needs.build_macos.outputs.zip_filename }},${{ needs.build_windows.outputs.zip_filename }}"
token: ${{ secrets.GITHUB_TOKEN }}
body: |
# New Release: ${{ steps.get_repo_name.outputs.repo_name }} ${{ steps.get_current_semver.outputs.semver }}
This is a new release of FolSum triggered by commit `${{ github.sha }}` on branch `${{ github.ref_name}}`.
## Changelog
- Add feature X
- Fix bug Y
- name: Generate "new release" message with a hyperlink to the release page
id: generate_release_message
run: echo "message=Created new release ${{ steps.get_current_semver.outputs.semver }} at ${{ steps.publish_release.outputs.html_url }}" >> $GITHUB_OUTPUT
- name: Create job summary about new release
run: echo "## ${{ steps.generate_release_message.outputs.message }}" >> $GITHUB_STEP_SUMMARY