|
| 1 | +# iOS App Store Build and Publish Workflow |
| 2 | +# This workflow builds the iOS app and uploads it to App Store Connect |
| 3 | +# |
| 4 | +# Required secrets: |
| 5 | +# - APPLE_CERTIFICATE_P12: Base64 encoded distribution certificate (.p12) |
| 6 | +# - APPLE_CERTIFICATE_PASSWORD: Password for the .p12 certificate |
| 7 | +# - APPLE_PROVISIONING_PROFILE: Base64 encoded provisioning profile (.mobileprovision) |
| 8 | +# - APPLE_PROVISIONING_PROFILE_WATCHKIT: Base64 encoded WatchKit provisioning profile |
| 9 | +# - APPLE_PROVISIONING_PROFILE_WATCHKIT_EXTENSION: Base64 encoded WatchKit Extension provisioning profile |
| 10 | +# - APP_STORE_CONNECT_API_KEY_ID: App Store Connect API Key ID |
| 11 | +# - APP_STORE_CONNECT_API_ISSUER_ID: App Store Connect API Issuer ID |
| 12 | +# - APP_STORE_CONNECT_API_KEY_P8: Base64 encoded App Store Connect API Key (.p8) |
| 13 | +# - KEYCHAIN_PASSWORD: A password for the temporary keychain |
| 14 | + |
| 15 | +name: iOS App Store |
| 16 | + |
| 17 | +on: |
| 18 | + workflow_dispatch: |
| 19 | + inputs: |
| 20 | + upload_to_testflight: |
| 21 | + description: 'Upload to TestFlight after build' |
| 22 | + required: true |
| 23 | + default: 'true' |
| 24 | + type: boolean |
| 25 | + build_number_increment: |
| 26 | + description: 'Increment build number (leave empty to keep current)' |
| 27 | + required: false |
| 28 | + type: string |
| 29 | + |
| 30 | +env: |
| 31 | + XCODE_PROJECT_PATH: 'build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj' |
| 32 | + SCHEME_NAME: 'qdomyoszwift' |
| 33 | + BUNDLE_ID: 'org.cagnulein.qdomyoszwift' |
| 34 | + TEAM_ID: '6335M7T29D' |
| 35 | + |
| 36 | +jobs: |
| 37 | + build-and-upload: |
| 38 | + runs-on: macos-14 |
| 39 | + |
| 40 | + steps: |
| 41 | + - name: Checkout repository |
| 42 | + uses: actions/checkout@v4 |
| 43 | + |
| 44 | + - name: Checkout SmtpClient submodule |
| 45 | + uses: actions/checkout@v4 |
| 46 | + with: |
| 47 | + repository: bluetiger9/SmtpClient-for-Qt |
| 48 | + path: "src/smtpclient/" |
| 49 | + ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c |
| 50 | + |
| 51 | + - name: Checkout googletest submodule |
| 52 | + uses: actions/checkout@v4 |
| 53 | + with: |
| 54 | + repository: google/googletest |
| 55 | + path: "tst/googletest/" |
| 56 | + ref: "release-1.12.1" |
| 57 | + |
| 58 | + - name: Select Xcode version |
| 59 | + run: sudo xcode-select -s /Applications/Xcode_15.2.app |
| 60 | + |
| 61 | + - name: Show Xcode version |
| 62 | + run: xcodebuild -version |
| 63 | + |
| 64 | + # Install Apple certificates and provisioning profiles |
| 65 | + - name: Install Apple Certificate |
| 66 | + env: |
| 67 | + APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} |
| 68 | + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} |
| 69 | + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} |
| 70 | + run: | |
| 71 | + # Create variables |
| 72 | + CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 |
| 73 | + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db |
| 74 | +
|
| 75 | + # Decode certificate from base64 |
| 76 | + echo -n "$APPLE_CERTIFICATE_P12" | base64 --decode -o $CERTIFICATE_PATH |
| 77 | +
|
| 78 | + # Create temporary keychain |
| 79 | + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH |
| 80 | + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH |
| 81 | + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH |
| 82 | +
|
| 83 | + # Import certificate to keychain |
| 84 | + security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH |
| 85 | + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH |
| 86 | + security list-keychain -d user -s $KEYCHAIN_PATH |
| 87 | +
|
| 88 | + - name: Install Provisioning Profiles |
| 89 | + env: |
| 90 | + APPLE_PROVISIONING_PROFILE: ${{ secrets.APPLE_PROVISIONING_PROFILE }} |
| 91 | + APPLE_PROVISIONING_PROFILE_WATCHKIT: ${{ secrets.APPLE_PROVISIONING_PROFILE_WATCHKIT }} |
| 92 | + APPLE_PROVISIONING_PROFILE_WATCHKIT_EXTENSION: ${{ secrets.APPLE_PROVISIONING_PROFILE_WATCHKIT_EXTENSION }} |
| 93 | + run: | |
| 94 | + # Create Provisioning Profiles directory |
| 95 | + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles |
| 96 | +
|
| 97 | + # Decode and install main app provisioning profile |
| 98 | + echo -n "$APPLE_PROVISIONING_PROFILE" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/main.mobileprovision |
| 99 | +
|
| 100 | + # Decode and install WatchKit provisioning profile (if provided) |
| 101 | + if [ -n "$APPLE_PROVISIONING_PROFILE_WATCHKIT" ]; then |
| 102 | + echo -n "$APPLE_PROVISIONING_PROFILE_WATCHKIT" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/watchkit.mobileprovision |
| 103 | + fi |
| 104 | +
|
| 105 | + # Decode and install WatchKit Extension provisioning profile (if provided) |
| 106 | + if [ -n "$APPLE_PROVISIONING_PROFILE_WATCHKIT_EXTENSION" ]; then |
| 107 | + echo -n "$APPLE_PROVISIONING_PROFILE_WATCHKIT_EXTENSION" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/watchkit_extension.mobileprovision |
| 108 | + fi |
| 109 | +
|
| 110 | + # Increment build number if requested |
| 111 | + - name: Increment Build Number |
| 112 | + if: ${{ inputs.build_number_increment != '' }} |
| 113 | + run: | |
| 114 | + # Update CURRENT_PROJECT_VERSION in project.pbxproj |
| 115 | + NEW_BUILD_NUMBER="${{ inputs.build_number_increment }}" |
| 116 | + sed -i '' "s/CURRENT_PROJECT_VERSION = [0-9]*;/CURRENT_PROJECT_VERSION = $NEW_BUILD_NUMBER;/g" "$XCODE_PROJECT_PATH/project.pbxproj" |
| 117 | + echo "Updated build number to: $NEW_BUILD_NUMBER" |
| 118 | +
|
| 119 | + # Create secrets file |
| 120 | + - name: Create secrets file |
| 121 | + run: | |
| 122 | + cd src |
| 123 | + echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h |
| 124 | + echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" >> secret.h |
| 125 | + echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h |
| 126 | + echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h |
| 127 | + echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h |
| 128 | + echo "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h |
| 129 | + echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h |
| 130 | + echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js |
| 131 | +
|
| 132 | + # Build for Release (App Store) |
| 133 | + - name: Build Archive |
| 134 | + run: | |
| 135 | + xcodebuild archive \ |
| 136 | + -project "$XCODE_PROJECT_PATH" \ |
| 137 | + -scheme "$SCHEME_NAME" \ |
| 138 | + -configuration Release \ |
| 139 | + -destination "generic/platform=iOS" \ |
| 140 | + -archivePath $RUNNER_TEMP/qdomyoszwift.xcarchive \ |
| 141 | + CODE_SIGN_STYLE=Manual \ |
| 142 | + DEVELOPMENT_TEAM=$TEAM_ID \ |
| 143 | + CODE_SIGN_IDENTITY="iPhone Distribution" \ |
| 144 | + -allowProvisioningUpdates |
| 145 | +
|
| 146 | + # Create ExportOptions.plist for App Store distribution |
| 147 | + - name: Create ExportOptions |
| 148 | + run: | |
| 149 | + cat > $RUNNER_TEMP/ExportOptions.plist << EOF |
| 150 | + <?xml version="1.0" encoding="UTF-8"?> |
| 151 | + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 152 | + <plist version="1.0"> |
| 153 | + <dict> |
| 154 | + <key>method</key> |
| 155 | + <string>app-store-connect</string> |
| 156 | + <key>teamID</key> |
| 157 | + <string>${TEAM_ID}</string> |
| 158 | + <key>uploadSymbols</key> |
| 159 | + <true/> |
| 160 | + <key>destination</key> |
| 161 | + <string>upload</string> |
| 162 | + </dict> |
| 163 | + </plist> |
| 164 | + EOF |
| 165 | +
|
| 166 | + # Export IPA |
| 167 | + - name: Export IPA |
| 168 | + run: | |
| 169 | + xcodebuild -exportArchive \ |
| 170 | + -archivePath $RUNNER_TEMP/qdomyoszwift.xcarchive \ |
| 171 | + -exportOptionsPlist $RUNNER_TEMP/ExportOptions.plist \ |
| 172 | + -exportPath $RUNNER_TEMP/export \ |
| 173 | + -allowProvisioningUpdates |
| 174 | +
|
| 175 | + # Setup App Store Connect API Key |
| 176 | + - name: Setup App Store Connect API Key |
| 177 | + if: ${{ inputs.upload_to_testflight == true }} |
| 178 | + env: |
| 179 | + APP_STORE_CONNECT_API_KEY_P8: ${{ secrets.APP_STORE_CONNECT_API_KEY_P8 }} |
| 180 | + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} |
| 181 | + APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} |
| 182 | + run: | |
| 183 | + mkdir -p ~/.appstoreconnect/private_keys |
| 184 | + echo -n "$APP_STORE_CONNECT_API_KEY_P8" | base64 --decode -o ~/.appstoreconnect/private_keys/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8 |
| 185 | +
|
| 186 | + # Upload to App Store Connect / TestFlight |
| 187 | + - name: Upload to TestFlight |
| 188 | + if: ${{ inputs.upload_to_testflight == true }} |
| 189 | + env: |
| 190 | + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} |
| 191 | + APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} |
| 192 | + run: | |
| 193 | + xcrun altool --upload-app \ |
| 194 | + --type ios \ |
| 195 | + --file "$RUNNER_TEMP/export/qdomyoszwift.ipa" \ |
| 196 | + --apiKey "$APP_STORE_CONNECT_API_KEY_ID" \ |
| 197 | + --apiIssuer "$APP_STORE_CONNECT_API_ISSUER_ID" |
| 198 | +
|
| 199 | + # Upload IPA as artifact (always, for backup) |
| 200 | + - name: Upload IPA Artifact |
| 201 | + uses: actions/upload-artifact@v4 |
| 202 | + with: |
| 203 | + name: qdomyoszwift-ipa |
| 204 | + path: ${{ runner.temp }}/export/*.ipa |
| 205 | + retention-days: 30 |
| 206 | + |
| 207 | + # Upload dSYM for crash reporting |
| 208 | + - name: Upload dSYM Artifact |
| 209 | + uses: actions/upload-artifact@v4 |
| 210 | + with: |
| 211 | + name: qdomyoszwift-dsym |
| 212 | + path: ${{ runner.temp }}/qdomyoszwift.xcarchive/dSYMs |
| 213 | + retention-days: 30 |
| 214 | + |
| 215 | + # Cleanup keychain |
| 216 | + - name: Cleanup |
| 217 | + if: always() |
| 218 | + run: | |
| 219 | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true |
| 220 | + rm -f $RUNNER_TEMP/certificate.p12 || true |
| 221 | + rm -rf ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision || true |
| 222 | + rm -rf ~/.appstoreconnect || true |
0 commit comments