feat: enhance macOS and Android CI workflows for app packaging

- Updated the package-and-publish.yml to improve macOS app re-signing by adding certificate handling and keychain management.
- Modified package-android.yml to transition from APK to AAB packaging, including updates to artifact handling and Google Play upload.
- Enhanced the bundle-tdlib-macos.sh script to fix binary rpaths, ensuring proper runtime behavior of bundled frameworks.
This commit is contained in:
Steven Enamakel
2026-02-05 01:05:55 +05:30
parent 76ef0e7f3a
commit e71bdc17ae
3 changed files with 93 additions and 49 deletions
+31 -4
View File
@@ -340,16 +340,37 @@ jobs:
- name: Re-sign macOS app after TDLib bundling
if: matrix.settings.platform == 'macos-latest'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
TARGET="${{ matrix.settings.target }}"
echo "Re-signing for target: $TARGET"
if [ -z "$APPLE_SIGNING_IDENTITY" ]; then
echo "No signing identity provided, skipping re-sign"
if [ -z "$APPLE_SIGNING_IDENTITY" ] || [ -z "$APPLE_CERTIFICATE" ]; then
echo "No signing identity or certificate provided, skipping re-sign"
exit 0
fi
# Import certificate into a temporary keychain (same approach as tauri-action)
KEYCHAIN_PATH="$RUNNER_TEMP/tdlib-signing.keychain-db"
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
# Create and configure keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
CERT_PATH="$RUNNER_TEMP/certificate.p12"
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
rm "$CERT_PATH"
# Add keychain to search list
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Find app bundle in target-specific or default location
if [ -n "$TARGET" ]; then
APP_BUNDLE=$(find "src-tauri/target/$TARGET/release/bundle/macos" -name "*.app" -type d 2>/dev/null | head -1)
@@ -364,16 +385,22 @@ jobs:
for dylib in "$APP_BUNDLE/Contents/Frameworks/"*.dylib; do
if [ -f "$dylib" ]; then
echo "Signing: $dylib"
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$dylib" || true
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$dylib"
fi
done
# Re-sign the entire app bundle
codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$APP_BUNDLE" || true
codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$APP_BUNDLE"
echo "App re-signed successfully"
# Verify signature
codesign --verify --verbose=2 "$APP_BUNDLE" || echo "Warning: Signature verification had issues"
else
echo "Warning: No app bundle found to re-sign"
fi
# Cleanup keychain
security delete-keychain "$KEYCHAIN_PATH" || true
# Re-create DMG after TDLib bundling (the original DMG was created before bundling)
- name: Recreate DMG with bundled TDLib
if: matrix.settings.platform == 'macos-latest'
+41 -41
View File
@@ -87,14 +87,14 @@ jobs:
if [ "$HTTP_CODE" = "200" ]; then
RELEASE_ID=$(echo "$BODY" | jq -r '.id')
IS_DRAFT=$(echo "$BODY" | jq -r '.draft')
# Check if Android APK is already uploaded
APK_EXISTS=$(echo "$BODY" | jq -r '.assets[]? | select(.name | endswith(".apk")) | .name' | head -1)
if [ -n "$APK_EXISTS" ]; then
echo "Android APK already exists in release: $APK_EXISTS"
# Check if Android AAB is already uploaded
AAB_EXISTS=$(echo "$BODY" | jq -r '.assets[]? | select(.name | endswith(".aab")) | .name' | head -1)
if [ -n "$AAB_EXISTS" ]; then
echo "Android AAB already exists in release: $AAB_EXISTS"
echo "should-skip=true" >> $GITHUB_OUTPUT
echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT
else
echo "Release found (ID: $RELEASE_ID, draft: $IS_DRAFT), no APK yet"
echo "Release found (ID: $RELEASE_ID, draft: $IS_DRAFT), no AAB yet"
echo "should-skip=false" >> $GITHUB_OUTPUT
echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT
fi
@@ -108,7 +108,7 @@ jobs:
echo "release-id=" >> $GITHUB_OUTPUT
fi
build-android:
name: Build and package Android APK
name: Build and package Android AAB
needs: [get-version, check-release]
if: ${{ !failure() && !cancelled() && needs.check-release.outputs.should-skip != 'true' }}
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
@@ -191,7 +191,7 @@ jobs:
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
run: |
echo "$ANDROID_KEYSTORE" | base64 --decode > ${{ github.workspace }}/release.jks
- name: Build Android APK (signed)
- name: Build Android AAB (signed)
if: needs.get-version.outputs.should-publish != ''
env:
BASE_URL: ${{ vars.BASE_URL }}
@@ -199,54 +199,54 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: npx tauri android build --apk true
run: npx tauri android build --aab true
- name: Build Android APK (unsigned)
- name: Build Android AAB (unsigned)
if: needs.get-version.outputs.should-publish == ''
env:
BASE_URL: ${{ vars.BASE_URL }}
run: npx tauri android build --apk true
run: npx tauri android build --aab true
- name: Find APK
id: find-apk
- name: Find AAB
id: find-aab
run: |
APK_ROOT="src-tauri/gen/android"
echo "Searching for APKs in $APK_ROOT..."
find "$APK_ROOT" -name '*.apk' -type f | sort
# Prefer universal release, then any signed release, then any APK (excluding unsigned)
APK_PATH=$(find "$APK_ROOT" -name '*-universal-release.apk' -type f | head -1)
if [ -z "$APK_PATH" ]; then
APK_PATH=$(find "$APK_ROOT" -name '*-release.apk' -not -name '*-unsigned.apk' -type f | head -1)
fi
if [ -z "$APK_PATH" ]; then
APK_PATH=$(find "$APK_ROOT" -name '*-release*.apk' -not -name '*-unsigned.apk' -type f | head -1)
fi
if [ -z "$APK_PATH" ]; then
APK_PATH=$(find "$APK_ROOT" -name '*.apk' -not -name '*-unsigned.apk' -type f | head -1)
fi
if [ -z "$APK_PATH" ]; then
APK_PATH=$(find "$APK_ROOT" -name '*.apk' -type f | head -1)
fi
if [ -z "$APK_PATH" ]; then
echo "No APK found"
AAB_ROOT="src-tauri/gen/android"
echo "Searching for AABs in $AAB_ROOT..."
find "$AAB_ROOT" -name '*.aab' -type f | sort
AAB_PATH=$(find "$AAB_ROOT" -name '*.aab' -type f | head -1)
if [ -z "$AAB_PATH" ]; then
echo "No AAB found"
exit 1
fi
APK_FILENAME=$(basename "$APK_PATH")
echo "Selected APK: $APK_PATH"
echo "path=$APK_PATH" >> $GITHUB_OUTPUT
echo "filename=$APK_FILENAME" >> $GITHUB_OUTPUT
- name: Upload APK artifact
AAB_FILENAME=$(basename "$AAB_PATH")
echo "Selected AAB: $AAB_PATH"
echo "path=$AAB_PATH" >> $GITHUB_OUTPUT
echo "filename=$AAB_FILENAME" >> $GITHUB_OUTPUT
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with:
name: android-apk
path: ${{ steps.find-apk.outputs.path }}
name: android-aab
path: ${{ steps.find-aab.outputs.path }}
- name: Upload APK to release
- name: Upload AAB to release
if: needs.get-version.outputs.should-publish == 'true' && needs.check-release.outputs.release-id != ''
env:
RELEASE_ID: ${{ needs.check-release.outputs.release-id }}
run: |
curl -X POST -H "Authorization: Bearer $XGH_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${{ steps.find-apk.outputs.path }}" \
"https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=${{ steps.find-apk.outputs.filename }}"
--data-binary "@${{ steps.find-aab.outputs.path }}" \
"https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=${{ steps.find-aab.outputs.filename }}"
# Requires secret GOOGLE_PLAY_SERVICE_ACCOUNT_JSON (full JSON key from Play Console API access)
# Track: internal | alpha | beta | production (use vars.GOOGLE_PLAY_TRACK to override)
- name: Upload AAB to Google Play
if: needs.get-version.outputs.should-publish == 'true' && secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON != ''
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.alphahuman.app
releaseFiles: ${{ steps.find-aab.outputs.path }}
track: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
releaseName: AlphaHuman v${{ needs.get-version.outputs.package-version }}
status: completed
+21 -4
View File
@@ -184,13 +184,30 @@ for dylib in "${FRAMEWORKS_DIR}"/*.dylib; do
fi
done
# Add rpath to the binary to look in Frameworks
# Fix rpaths in the binary
echo ""
echo "Adding rpath to binary..."
# First, try to delete existing rpath if it exists (ignore errors)
echo "Fixing binary rpaths..."
# Get all current rpaths
CURRENT_RPATHS=$(otool -l "$BINARY_PATH" | grep -A2 "cmd LC_RPATH" | grep "path " | awk '{print $2}')
echo "Current rpaths:"
echo "$CURRENT_RPATHS"
# Remove all rpaths that point to build directories (they won't exist at runtime)
for rpath in $CURRENT_RPATHS; do
# Remove rpaths containing /build/, /target/, or absolute paths that aren't @executable_path
if [[ "$rpath" == *"/build/"* ]] || [[ "$rpath" == *"/target/"* ]] || [[ "$rpath" != @* ]]; then
echo "Removing build-time rpath: $rpath"
install_name_tool -delete_rpath "$rpath" "$BINARY_PATH" 2>/dev/null || true
fi
done
# Delete our target rpath first if it exists (to avoid "already exists" error)
install_name_tool -delete_rpath "@executable_path/../Frameworks" "$BINARY_PATH" 2>/dev/null || true
# Add the correct rpath
# Add the correct rpath for the bundled Frameworks
echo "Adding rpath: @executable_path/../Frameworks"
install_name_tool -add_rpath "@executable_path/../Frameworks" "$BINARY_PATH"
# Verify the changes