Skip to content

Build AppFlowy macOS #199

Build AppFlowy macOS

Build AppFlowy macOS #199

Workflow file for this run

name: Build AppFlowy macOS
on:
workflow_dispatch:
inputs:
repo:
description: "Repo"
required: true
default: "AppFlowy-IO/AppFlowy"
branch:
description: "Branch"
required: true
default: "main"
build_name:
description: "Build Version (it should match the version in pubspec.yaml)"
required: true
default: "0.9.4"
arch:
type: choice
description: "Build Architecture"
required: true
options:
- All
- x86_64
- aarch64
- universal
internal_build:
type: choice
description: "Internal Build Type (1 for internal, 0 for external)"
required: true
default: "1"
options:
- "0"
- "1"
workflow_call:
inputs:
repo:
description: "Repo"
required: true
type: string
branch:
description: "Branch"
required: true
type: string
build_name:
description: "Build Version"
required: true
type: string
arch:
description: "Build Architecture"
required: true
type: string
internal_build:
description: "Internal Build Type"
required: true
type: string
upload_url:
description: "Upload URL for release assets"
required: false
type: string
env:
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.85.0"
jobs:
x86_64:
runs-on: macos-13
if: ${{ inputs.arch == 'All' || inputs.arch == 'x86_64' }}
env:
MACOS_APP_RELEASE_PATH: frontend/appflowy_flutter/product/${{ inputs.build_name }}/macos/Release
MACOS_X86_ZIP_NAME: AppFlowy-${{ inputs.build_name }}-macos-x86_64.zip
MACOS_DMG_NAME: AppFlowy-${{ inputs.build_name }}-macos-x86_64
steps:
- name: Checkout source code
uses: actions/checkout@v2
with:
repository: ${{ inputs.repo }}
ref: ${{ inputs.branch }}
token: ${{ secrets.PRIVATE_REPO_TOKEN }}
- name: Install flutter
uses: subosito/flutter-action@v2
with:
cache: true
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
target: x86_64-apple-darwin
override: true
components: rustfmt
profile: minimal
- uses: Swatinem/rust-cache@v2
with:
prefix-key: macos-x86_64
workspaces: |
frontend/rust-lib
- uses: davidB/rust-cargo-make@v1
with:
version: "0.37.5"
- name: Install prerequisites
working-directory: frontend
run: |
cargo install duckscript_cli --force --locked
cargo make appflowy-flutter-deps-tools
- name: Generate env file
working-directory: frontend/appflowy_flutter
run: |
echo "INTERNAL_BUILD=${{ inputs.internal_build }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
shell: bash
- name: Configure Git credentials for Cargo
run: |
git config --global credential.helper store
echo "https://${{ secrets.ADMIN_GITHUB_TOKEN }}:x-oauth-basic@github.com" > ~/.git-credentials
- name: Build AppFlowy (x86_64)
working-directory: frontend
run: |
echo "🔧 Building AppFlowy for x86_64 (Intel)"
echo "Architecture: $(uname -m)"
echo "Rust targets: $(rustup target list --installed)"
flutter config --enable-macos-desktop
dart ./scripts/flutter_release_build/build_flowy.dart run . ${{ inputs.build_name }}
echo "✅ x86_64 build completed"
ls -la appflowy_flutter/product/${{ inputs.build_name }}/macos/Release/
- name: Codesign AppFlowy
run: |
if [ -z "${{ secrets.MACOS_CERTIFICATE_BASE64 }}" ]; then
echo "⚠️ Skipping code signing - MACOS_CERTIFICATE_BASE64 not set"
exit 0
fi
echo "✍️ Code signing AppFlowy app..."
echo ${{ secrets.MACOS_CERTIFICATE_BASE64 }} | base64 --decode > certificate.p12
security create-keychain -p action build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p action build.keychain
security import certificate.p12 -k build.keychain -P ${{ secrets.P12_PASSWORD }} -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k action build.keychain
# Verify the app exists before signing
if [ ! -d "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" ]; then
echo "❌ AppFlowy.app not found at ${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
exit 1
fi
/usr/bin/codesign --force --options runtime --deep --sign "${{ secrets.MACOS_CODESIGN_ID }}" "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" -v
echo "✅ Code signing completed"
- name: Create macOS dmg
run: |
brew install create-dmg
i=0
until [[ -e "${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" ]]; do
create-dmg \
--volname ${{ env.MACOS_DMG_NAME }} \
--hide-extension "AppFlowy.app" \
--background frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
--window-size 600 450 \
--icon-size 94 \
--icon "AppFlowy.app" 141 249 \
--app-drop-link 458 249 \
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" || true
if [[ $i -eq 10 ]]; then
echo 'Error: create-dmg did not succeed even after 10 tries.'
exit 1
fi
i=$((i+1))
done
- name: Notarize AppFlowy
continue-on-error: true
run: |
xcrun notarytool submit ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg --apple-id ${{ secrets.MACOS_NOTARY_USER }} --team-id ${{ secrets.MACOS_TEAM_ID }} --password ${{ secrets.MACOS_NOTARY_PWD }} -v -f "json" --wait
- name: Archive Asset
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
- name: Upload ZIP
uses: actions/upload-artifact@v4
with:
name: ${{ env.MACOS_X86_ZIP_NAME }}
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }}
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: ${{ env.MACOS_DMG_NAME }}.dmg
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
- name: Upload ZIP to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_X86_ZIP_NAME }}"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
- name: Upload DMG to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_DMG_NAME }}.dmg"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
aarch64:
runs-on: macos-latest
if: ${{ inputs.arch == 'All' || inputs.arch == 'aarch64' }}
env:
MACOS_APP_RELEASE_PATH: frontend/appflowy_flutter/product/${{ inputs.build_name }}/macos/Release
MACOS_AARCH64_ZIP_NAME: AppFlowy-${{ inputs.build_name }}-macos-aarch64.zip
MACOS_DMG_NAME: AppFlowy-${{ inputs.build_name }}-macos-aarch64
steps:
- name: Checkout source code
uses: actions/checkout@v2
with:
repository: ${{ inputs.repo }}
ref: ${{ inputs.branch }}
token: ${{ secrets.PRIVATE_REPO_TOKEN }}
- name: Install flutter
uses: subosito/flutter-action@v2
with:
cache: true
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
target: aarch64-apple-darwin
override: true
components: rustfmt
profile: minimal
- uses: Swatinem/rust-cache@v2
with:
prefix-key: macos-aarch64
workspaces: |
frontend/rust-lib
- uses: davidB/rust-cargo-make@v1
with:
version: "0.37.5"
- name: Install prerequisites
working-directory: frontend
run: |
cargo install duckscript_cli --force --locked
cargo make appflowy-flutter-deps-tools
- name: Generate env file
working-directory: frontend/appflowy_flutter
run: |
echo "INTERNAL_BUILD=${{ inputs.internal_build }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
shell: bash
- name: Configure Git credentials for Cargo
run: |
git config --global credential.helper store
echo "https://${{ secrets.ADMIN_GITHUB_TOKEN }}:x-oauth-basic@github.com" > ~/.git-credentials
- name: Build AppFlowy (aarch64)
working-directory: frontend
run: |
echo "🔧 Building AppFlowy for aarch64 (ARM64)"
echo "Architecture: $(uname -m)"
echo "Rust targets: $(rustup target list --installed)"
flutter config --enable-macos-desktop
dart ./scripts/flutter_release_build/build_flowy.dart run . ${{ inputs.build_name }}
echo "✅ aarch64 build completed"
ls -la appflowy_flutter/product/${{ inputs.build_name }}/macos/Release/
- name: Codesign AppFlowy
run: |
if [ -z "${{ secrets.MACOS_CERTIFICATE_BASE64 }}" ]; then
echo "⚠️ Skipping code signing - MACOS_CERTIFICATE_BASE64 not set"
exit 0
fi
echo "✍️ Code signing AppFlowy app..."
echo ${{ secrets.MACOS_CERTIFICATE_BASE64 }} | base64 --decode > certificate.p12
security create-keychain -p action build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p action build.keychain
security import certificate.p12 -k build.keychain -P ${{ secrets.P12_PASSWORD }} -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k action build.keychain
# Verify the app exists before signing
if [ ! -d "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" ]; then
echo "❌ AppFlowy.app not found at ${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
exit 1
fi
/usr/bin/codesign --force --options runtime --deep --sign "${{ secrets.MACOS_CODESIGN_ID }}" "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" -v
echo "✅ Code signing completed"
- name: Create macOS dmg
run: |
brew install create-dmg
i=0
until [[ -e "${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" ]]; do
create-dmg \
--volname ${{ env.MACOS_DMG_NAME }} \
--hide-extension "AppFlowy.app" \
--background frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
--window-size 600 450 \
--icon-size 94 \
--icon "AppFlowy.app" 141 249 \
--app-drop-link 458 249 \
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" || true
if [[ $i -eq 10 ]]; then
echo 'Error: create-dmg did not succeed even after 10 tries.'
exit 1
fi
i=$((i+1))
done
- name: Notarize AppFlowy
continue-on-error: true
run: |
xcrun notarytool submit ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg --apple-id ${{ secrets.MACOS_NOTARY_USER }} --team-id ${{ secrets.MACOS_TEAM_ID }} --password ${{ secrets.MACOS_NOTARY_PWD }} -v -f "json" --wait
- name: Archive Asset
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
run: zip --symlinks -qr ${{ env.MACOS_AARCH64_ZIP_NAME }} AppFlowy.app
- name: Upload ZIP
uses: actions/upload-artifact@v4
with:
name: ${{ env.MACOS_AARCH64_ZIP_NAME }}
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_AARCH64_ZIP_NAME }}
- name: Upload DMG
uses: actions/upload-artifact@v4
continue-on-error: true
with:
name: ${{ env.MACOS_DMG_NAME }}.dmg
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
- name: Upload ZIP to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_AARCH64_ZIP_NAME }}"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
- name: Upload DMG to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_DMG_NAME }}.dmg"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
universal:
runs-on: macos-latest
if: ${{ inputs.arch == 'All' || inputs.arch == 'universal' }}
env:
MACOS_APP_RELEASE_PATH: frontend/appflowy_flutter/product/${{ inputs.build_name }}/macos/Release
MACOS_AARCH64_ZIP_NAME: AppFlowy-${{ inputs.build_name }}-macos-universal.zip
MACOS_DMG_NAME: AppFlowy-${{ inputs.build_name }}-macos-universal
steps:
- name: Checkout source code
uses: actions/checkout@v2
with:
repository: ${{ inputs.repo }}
ref: ${{ inputs.branch }}
token: ${{ secrets.PRIVATE_REPO_TOKEN }}
- name: Install flutter
uses: subosito/flutter-action@v2
with:
cache: true
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
components: rustfmt
profile: minimal
- name: Install Rust targets
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
- uses: Swatinem/rust-cache@v2
with:
prefix-key: macos-universal
workspaces: |
frontend/rust-lib
- uses: davidB/rust-cargo-make@v1
with:
version: "0.37.5"
- name: Install prerequisites
working-directory: frontend
run: |
cargo install duckscript_cli --force --locked
cargo make appflowy-flutter-deps-tools
- name: Generate env file
working-directory: frontend/appflowy_flutter
run: |
echo "INTERNAL_BUILD=${{ inputs.internal_build }}" >> .env
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
shell: bash
- name: Configure Git credentials for Cargo
run: |
git config --global credential.helper store
echo "https://${{ secrets.ADMIN_GITHUB_TOKEN }}:x-oauth-basic@github.com" > ~/.git-credentials
- name: Build AppFlowy (Universal)
working-directory: frontend
run: |
echo "🔧 Building AppFlowy Universal Binary (x86_64 + ARM64)"
echo "Architecture: $(uname -m)"
echo "Rust targets: $(rustup target list --installed)"
flutter config --enable-macos-desktop
# Ensure the script is executable
chmod +x scripts/flutter_release_build/build_universal_package_for_macos.sh
# Run the universal build script with verbose output
echo "📦 Starting universal build..."
sh scripts/flutter_release_build/build_universal_package_for_macos.sh ${{ inputs.build_name }}
echo "✅ Universal build completed"
ls -la appflowy_flutter/product/${{ inputs.build_name }}/macos/Release/
- name: Codesign AppFlowy
run: |
if [ -z "${{ secrets.MACOS_CERTIFICATE_BASE64 }}" ]; then
echo "⚠️ Skipping code signing - MACOS_CERTIFICATE_BASE64 not set"
exit 0
fi
echo "✍️ Code signing AppFlowy app..."
echo ${{ secrets.MACOS_CERTIFICATE_BASE64 }} | base64 --decode > certificate.p12
security create-keychain -p action build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p action build.keychain
security import certificate.p12 -k build.keychain -P ${{ secrets.P12_PASSWORD }} -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k action build.keychain
# Verify the app exists before signing
if [ ! -d "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" ]; then
echo "❌ AppFlowy.app not found at ${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
exit 1
fi
/usr/bin/codesign --force --options runtime --deep --sign "${{ secrets.MACOS_CODESIGN_ID }}" "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" -v
echo "✅ Code signing completed"
- name: Create macOS dmg
run: |
brew install create-dmg
i=0
until [[ -e "${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" ]]; do
create-dmg \
--volname ${{ env.MACOS_DMG_NAME }} \
--hide-extension "AppFlowy.app" \
--background frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
--window-size 600 450 \
--icon-size 94 \
--icon "AppFlowy.app" 141 249 \
--app-drop-link 458 249 \
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" || true
if [[ $i -eq 10 ]]; then
echo 'Error: create-dmg did not succeed even after 10 tries.'
exit 1
fi
i=$((i+1))
done
- name: Check for Secret availability
id: secret-check
shell: bash
run: |
if [ "${{ secrets.MACOS_NOTARY_USER }}" == '' ]; then
echo "available=false" >> $GITHUB_OUTPUT;
elif [ "${{ secrets.MACOS_TEAM_ID }}" == '' ]; then
echo "available=false" >> $GITHUB_OUTPUT;
elif [ "${{ secrets.MACOS_NOTARY_PWD }}" == '' ]; then
echo "available=false" >> $GITHUB_OUTPUT;
else
echo "available=true" >> $GITHUB_OUTPUT;
fi
- name: Notarize AppFlowy
if: ${{ steps.secret-check.outputs.available == 'true' }}
continue-on-error: true
run: |
xcrun notarytool submit ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg --apple-id ${{ secrets.MACOS_NOTARY_USER }} --team-id ${{ secrets.MACOS_TEAM_ID }} --password ${{ secrets.MACOS_NOTARY_PWD }} -v -f "json" --wait
- name: Archive Asset
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
run: zip --symlinks -qr ${{ env.MACOS_AARCH64_ZIP_NAME }} AppFlowy.app
- name: Upload ZIP
uses: actions/upload-artifact@v4
with:
name: ${{ env.MACOS_AARCH64_ZIP_NAME }}
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_AARCH64_ZIP_NAME }}
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: ${{ env.MACOS_DMG_NAME }}.dmg
path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
- name: Upload ZIP to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_AARCH64_ZIP_NAME }}"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body
- name: Upload DMG to Release
if: inputs.upload_url != ''
run: |
filename="${{ env.MACOS_DMG_NAME }}.dmg"
filepath="${{ env.MACOS_APP_RELEASE_PATH }}/$filename"
# URL encode the filename
encoded_filename=$(echo -n "$filename" | jq -sRr @uri)
# Remove the {?name,label} template from upload URL if present
upload_url=$(echo "${{ inputs.upload_url }}" | sed 's/{[^}]*}//g')
echo "Uploading $filename to release..."
curl -L \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$filepath" \
"${upload_url}?name=${encoded_filename}" \
--fail-with-body