Update CI #169
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Build and Release | |
on: | |
push: | |
branches-ignore: | |
- main | |
tags: | |
- "v*.*.*" | |
- "v*.*.*-beta.*" | |
pull_request: | |
branches: | |
- main | |
permissions: | |
contents: write | |
pages: write | |
id-token: write | |
env: | |
XCODE_VERSION: "16.2.0" | |
BUNDLE_ID: "com.ameba.SwiftKey" | |
APP_NAME: "SwiftKey" | |
BETA_FEED_URL: "https://swiftkey.app/appcast_beta.xml" | |
PROD_FEED_URL: "https://swiftkey.app/appcast.xml" | |
jobs: | |
verify-release-notes: | |
if: startsWith(github.ref, 'refs/tags/v') && github.event_name != 'pull_request' | |
runs-on: ubuntu-latest | |
outputs: | |
release_notes: ${{ steps.read_notes.outputs.content }} | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Get version | |
id: get_version | |
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT | |
- name: Check release notes exist | |
id: check_notes | |
run: | | |
VERSION=${{ steps.get_version.outputs.VERSION }} | |
NOTES_PATH="docs/release-notes/${VERSION}.md" | |
if [ ! -f "$NOTES_PATH" ]; then | |
echo "Error: Release notes not found for version ${VERSION}" | |
echo "Expected path: ${NOTES_PATH}" | |
exit 1 | |
fi | |
- name: Read release notes | |
id: read_notes | |
run: | | |
VERSION=${{ steps.get_version.outputs.VERSION }} | |
NOTES_PATH="docs/release-notes/${VERSION}.md" | |
CONTENT=$(cat "$NOTES_PATH" | perl -p -e 's/%/%25/g' | perl -p -e 's/\n/%0A/g' | perl -p -e 's/\r/%0D/g') | |
echo "content=$CONTENT" >> $GITHUB_OUTPUT | |
build: | |
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository | |
runs-on: macos-latest | |
timeout-minutes: 30 | |
outputs: | |
build_number: ${{ steps.get_commit_count.outputs.BUILD_NUMBER }} | |
steps: | |
- uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
- name: Get Version Numbers | |
id: get_versions | |
run: | | |
# Get build number from commit count | |
BUILD_NUMBER=$(git rev-list --count HEAD) | |
echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_OUTPUT | |
# Initialize version variables | |
VERSION="0.0.0" | |
IS_RELEASE=false | |
# Check for tag | |
if [ -n "$GITHUB_REF_TYPE" ]; then | |
case "$GITHUB_REF_TYPE" in | |
"tag") | |
if [[ "$GITHUB_REF_NAME" == v* ]]; then | |
VERSION=${GITHUB_REF_NAME#v} | |
IS_RELEASE=true | |
fi | |
;; | |
"branch") | |
VERSION="0.0.0" | |
IS_RELEASE=false | |
;; | |
esac | |
fi | |
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
echo "IS_RELEASE=$IS_RELEASE" >> $GITHUB_OUTPUT | |
# Debug output | |
echo "Debug information:" | |
echo "GITHUB_REF_TYPE: $GITHUB_REF_TYPE" | |
echo "GITHUB_REF_NAME: $GITHUB_REF_NAME" | |
echo "GITHUB_REF: $GITHUB_REF" | |
echo "BUILD_NUMBER: $BUILD_NUMBER" | |
echo "VERSION: $VERSION" | |
echo "IS_RELEASE: $IS_RELEASE" | |
- name: Setup Xcode | |
uses: maxim-lobanov/setup-xcode@v1 | |
with: | |
xcode-version: ${{ env.XCODE_VERSION }} | |
- name: Build Application | |
env: | |
MARKETING_VERSION: ${{ steps.get_versions.outputs.VERSION }} | |
CURRENT_PROJECT_VERSION: ${{ steps.get_versions.outputs.BUILD_NUMBER }} | |
run: | | |
xcodebuild \ | |
-scheme "${{ env.APP_NAME }}" \ | |
-configuration Release \ | |
-derivedDataPath build \ | |
-arch arm64 -arch x86_64 \ | |
ONLY_ACTIVE_ARCH=NO \ | |
MARKETING_VERSION="$MARKETING_VERSION" \ | |
CURRENT_PROJECT_VERSION="$CURRENT_PROJECT_VERSION" \ | |
build | |
- name: Verify Universal Binary | |
run: | | |
echo "Checking binary architectures..." | |
lipo -archs "build/Build/Products/Release/${{ env.APP_NAME }}.app/Contents/MacOS/${{ env.APP_NAME }}" | |
- name: Package App Bundle | |
run: | | |
cd build/Build/Products/Release | |
ditto -c -k --keepParent "${{ env.APP_NAME }}.app" "${{ env.APP_NAME }}.app.zip" | |
- name: Upload App Bundle | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Unsigned | |
path: build/Build/Products/Release/${{ env.APP_NAME }}.app.zip | |
retention-days: 5 | |
sign: | |
needs: build | |
runs-on: macos-latest | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Download Unsigned App | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Unsigned | |
path: . | |
- name: Unpack App Bundle | |
run: | | |
ditto -x -k "${{ env.APP_NAME }}.app.zip" . | |
- name: Sign Application | |
uses: ./.github/actions/sign | |
with: | |
certificate: ${{ secrets.CERTIFICATES_P12 }} | |
certificate-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} | |
apple-team-id: ${{ secrets.TEAM_ID }} | |
app-path: "${{ env.APP_NAME }}.app" | |
entitlements-path: "SwiftKey/Resources/SwiftKey.entitlements" | |
- name: Package Signed App Bundle | |
run: | | |
ditto -c -k --keepParent "${{ env.APP_NAME }}.app" "${{ env.APP_NAME }}.app.zip" | |
- name: Upload Signed App Bundle | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Signed | |
path: ${{ env.APP_NAME }}.app.zip | |
retention-days: 5 | |
notarize: | |
needs: sign | |
runs-on: macos-latest | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Download Signed App | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Signed | |
path: . | |
- name: Unpack App Bundle | |
run: | | |
ditto -x -k "${{ env.APP_NAME }}.app.zip" . | |
- name: Notarize Application | |
uses: ./.github/actions/notarize | |
with: | |
username: ${{ secrets.APPLE_ID }} | |
password: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
apple-team-id: ${{ secrets.TEAM_ID }} | |
app-path: "${{ env.APP_NAME }}.app" | |
- name: Package Notarized App Bundle | |
run: | | |
ditto -c -k --keepParent "${{ env.APP_NAME }}.app" "${{ env.APP_NAME }}.app.zip" | |
- name: Upload Notarized App Bundle | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Notarized | |
path: ${{ env.APP_NAME }}.app.zip | |
retention-days: 5 | |
release: | |
needs: [notarize, verify-release-notes] | |
if: startsWith(github.ref, 'refs/tags/v') | |
runs-on: macos-latest | |
steps: | |
- uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 # Need full history for changelog | |
- name: Set version variables | |
run: | | |
echo "VERSION=$(echo ${{ github.ref_name }} | sed 's/v//')" >> $GITHUB_ENV | |
echo "IS_BETA=$(echo ${{ github.ref_name }} | grep -q 'beta' && echo 'true' || echo 'false')" >> $GITHUB_ENV | |
echo "TAG=$(echo ${{ github.ref_name }})" >> $GITHUB_ENV | |
- name: Download Notarized App | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ env.APP_NAME }}-Notarized | |
path: . | |
- name: Unpack App Bundle | |
run: | | |
ditto -x -k "${{ env.APP_NAME }}.app.zip" . | |
- name: Create Release Archive | |
run: | | |
ditto -c -k --keepParent "${{ env.APP_NAME }}.app" "${{ env.APP_NAME }}-${{ env.VERSION }}.zip" | |
- name: Calculate SHA256 | |
run: | | |
echo "ZIP_SHA=$(shasum -a 256 ${{ env.APP_NAME }}-${{ env.VERSION }}.zip | cut -d' ' -f1)" >> $GITHUB_ENV | |
- name: Read Release Notes | |
id: release_notes | |
run: | | |
NOTES_PATH="docs/release-notes/${{ env.TAG }}.md" | |
if [ ! -f "$NOTES_PATH" ]; then | |
echo "Error: Release notes not found at $NOTES_PATH" | |
exit 1 | |
fi | |
# Escape the content for GitHub Actions output | |
CONTENT=$(cat "$NOTES_PATH" | perl -p -e 's/%/%25/g' | perl -p -e 's/\n/%0A/g' | perl -p -e 's/\r/%0D/g') | |
echo "content=$CONTENT" >> $GITHUB_OUTPUT | |
# Also store raw content for the release | |
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV | |
cat "$NOTES_PATH" >> $GITHUB_ENV | |
echo "EOF" >> $GITHUB_ENV | |
- name: Generate Sparkle Signature | |
id: generate_signature | |
uses: ./.github/actions/sparkle-sign | |
with: | |
private-key: ${{ secrets.SPARKLE_PRIVATE_KEY }} | |
file-path: ${{ env.APP_NAME }}-${{ env.VERSION }}.zip | |
- name: Update Appcast | |
run: | | |
IS_BETA="${{ env.IS_BETA }}" | |
APPCAST_NAME="appcast$([ "$IS_BETA" == "true" ] && echo "_beta").xml" | |
APPCAST_FILE="docs/$APPCAST_NAME" | |
# Determine feed URL explicitly | |
if [ "$IS_BETA" == "true" ]; then | |
FEED_URL="${{ env.BETA_FEED_URL }}" | |
else | |
FEED_URL="${{ env.PROD_FEED_URL }}" | |
fi | |
cat > "$APPCAST_FILE" <<-EOF | |
<?xml version="1.0" encoding="utf-8"?> | |
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/"> | |
<channel> | |
<title>${{ env.APP_NAME }} $([ "$IS_BETA" == "true" ] && echo "Beta ")Appcast</title> | |
<link>$FEED_URL</link> | |
<description>${{ env.APP_NAME }} $([ "$IS_BETA" == "true" ] && echo "Beta ")Updates</description> | |
<language>en</language> | |
<item> | |
<title>${{ env.APP_NAME }} ${{ env.VERSION }}</title> | |
<sparkle:version>${{ env.VERSION }}</sparkle:version> | |
<sparkle:shortVersionString>${{ env.VERSION }}</sparkle:shortVersionString> | |
<description><![CDATA[${{ env.RELEASE_NOTES }}]]></description> | |
<pubDate>$(date -R)</pubDate> | |
<enclosure | |
url="https://github.com/${{ github.repository }}/releases/download/${{ env.TAG }}/${{ env.APP_NAME }}-${{ env.VERSION }}.zip" | |
<sparkle:version>${{ env.BUILD_NUMBER }}</sparkle:version> | |
<sparkle:shortVersionString>${{ env.VERSION }}</sparkle:shortVersionString> | |
type="application/octet-stream" | |
${{ steps.generate_signature.outputs.signature }} | |
sparkle:sha256="${{ env.ZIP_SHA }}" | |
/> | |
</item> | |
</channel> | |
</rss> | |
EOF | |
echo "Created appcast file at $APPCAST_FILE with FEED_URL=$FEED_URL" | |
echo "IS_BETA=$IS_BETA" | |
cat "$APPCAST_FILE" | |
- name: Update CHANGELOG.md | |
run: | | |
# Create temporary file | |
TEMP_FILE=$(mktemp) | |
# Add new release notes with header | |
echo "# ${{ env.TAG }} ($(date +'%Y-%m-%d'))" > "$TEMP_FILE" | |
echo "" >> "$TEMP_FILE" | |
echo "${{ env.RELEASE_NOTES }}" >> "$TEMP_FILE" | |
echo "" >> "$TEMP_FILE" | |
# Append existing changelog if it exists | |
if [ -f "CHANGELOG.md" ]; then | |
cat "CHANGELOG.md" >> "$TEMP_FILE" | |
fi | |
# Replace original changelog | |
mv "$TEMP_FILE" "CHANGELOG.md" | |
- name: Commit and Push Changes | |
run: | | |
git config --local user.email "[email protected]" | |
git config --local user.name "GitHub Action" | |
git add CHANGELOG.md | |
git add docs/appcast*.xml | |
git commit -m "docs: update CHANGELOG.md and appcast for ${{ env.TAG }}" | |
git push origin HEAD:main | |
- name: Create GitHub Release | |
uses: softprops/action-gh-release@v1 | |
with: | |
files: ${{ env.APP_NAME }}-${{ env.VERSION }}.zip | |
body: ${{ env.RELEASE_NOTES }} | |
prerelease: ${{ env.IS_BETA == 'true' }} | |
draft: false | |
fail_on_unmatched_files: true | |
generate_release_notes: false | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Deploy to GitHub Pages | |
uses: peaceiris/actions-gh-pages@v3 | |
with: | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
publish_dir: ./docs | |
keep_files: true | |
notify: | |
needs: [build, sign, notarize] | |
if: always() | |
runs-on: ubuntu-latest | |
steps: | |
- name: Notify Status | |
run: | | |
echo "Build status: ${{ needs.build.result }}" | |
echo "Sign status: ${{ needs.sign.result }}" | |
echo "Notarize status: ${{ needs.notarize.result }}" |