diff --git a/.github/workflows/buildAndroid.yml b/.github/workflows/buildAndroid.yml index a8023aebd359..32a8d2c2812d 100644 --- a/.github/workflows/buildAndroid.yml +++ b/.github/workflows/buildAndroid.yml @@ -41,7 +41,6 @@ on: description: Git ref to checkout and build required: true type: string - pull_request_number: description: The pull request number associated with this build, if relevant. type: number diff --git a/.github/workflows/buildIOS.yml b/.github/workflows/buildIOS.yml new file mode 100644 index 000000000000..2386d01da793 --- /dev/null +++ b/.github/workflows/buildIOS.yml @@ -0,0 +1,155 @@ +name: Build iOS app + +on: + workflow_call: + inputs: + type: + description: 'What type of build to run. Must be one of ["release", "adhoc"]' + type: string + required: true + ref: + description: Git ref to checkout and build + type: string + required: true + pull_request_number: + description: The pull request number associated with this build, if relevant. + type: string + required: false + outputs: + IPA_FILE_NAME: + value: ${{ jobs.build.outputs.IPA_FILE_NAME }} + DSYM_FILE_NAME: + value: ${{ jobs.build.outputs.DSYM_FILE_NAME }} + + workflow_dispatch: + inputs: + type: + description: What type of build do you want to run? + required: true + type: choice + options: + - release + - adhoc + ref: + description: Git ref to checkout and build + required: true + type: string + pull_request_number: + description: The pull request number associated with this build, if relevant. + type: number + required: false + +jobs: + build: + name: Build iOS app + runs-on: macos-13-xlarge + env: + DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer + outputs: + IPA_FILE_NAME: ${{ steps.build.outputs.IPA_FILE_NAME }} + DSYM_FILE_NAME: ${{ steps.build.outputs.DSYM_FILE_NAME }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + + - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it + if: ${{ inputs.type == 'adhoc' }} + run: | + cp .env.staging .env.adhoc + sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc + echo "PULL_REQUEST_NUMBER=${{ inputs.pull_request_number }}" >> .env.adhoc + + - name: Configure MapBox SDK + run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} + + - name: Setup Node + id: setup-node + uses: ./.github/actions/composite/setupNode + + - name: Setup Ruby + uses: ruby/setup-ruby@v1.190.0 + with: + bundler-cache: true + + - name: Cache Pod dependencies + uses: actions/cache@v4 + id: pods-cache + with: + path: ios/Pods + key: ${{ runner.os }}-pods-cache-${{ hashFiles('ios/Podfile.lock', 'firebase.json') }} + + - name: Compare Podfile.lock and Manifest.lock + id: compare-podfile-and-manifest + run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('ios/Podfile.lock') == hashFiles('ios/Pods/Manifest.lock') }}" >> "$GITHUB_OUTPUT" + + - name: Install cocoapods + uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 + if: steps.pods-cache.outputs.cache-hit != 'true' || steps.compare-podfile-and-manifest.outputs.IS_PODFILE_SAME_AS_MANIFEST != 'true' || steps.setup-node.outputs.cache-hit != 'true' + with: + timeout_minutes: 10 + max_attempts: 5 + command: scripts/pod-install.sh + + - name: Decrypt provisioning profiles + run: | + cd ios + provisioningProfile='' + if [ '${{ inputs.type }}' == 'release' ]; then + provisioningProfile='NewApp_AppStore' + else + provisioningProfile='NewApp_AdHoc' + fi + echo "Using provisioning profile: $provisioningProfile" + gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output "$provisioningProfile.mobileprovision" "$provisioningProfile.mobileprovision.gpg" + gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output "${provisioningProfile}_Notification_Service.mobileprovision" "${provisioningProfile}_Notification_Service.mobileprovision.gpg" + env: + LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + + - name: Decrypt code signing certificate + run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg + env: + LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + + - name: Build iOS ${{ inputs.type }} app + id: build + run: | + lane='' + if [ '${{ inputs.type }}' == 'release' ]; then + lane='build' + else + lane='build_adhoc' + fi + + bundle exec fastlane ios "$lane" + + # Reload environment variables from GITHUB_ENV + # shellcheck disable=SC1090 + source "$GITHUB_ENV" + + { + # ipaPath and dsymPath are environment variables set within the Fastfile + echo "IPA_PATH=$ipaPath" + echo "IPA_FILE_NAME=$(basename "$ipaPath")" + echo "DSYM_PATH=$dsymPath" + echo "DSYM_FILE_NAME=$(basename "$dsymPath")" + } >> "$GITHUB_OUTPUT" + + - name: Upload iOS build artifact + uses: actions/upload-artifact@v4 + with: + name: ios-artifact-ipa + path: ${{ steps.build.outputs.IPA_PATH }} + + - name: Upload iOS debug symbols artifact + uses: actions/upload-artifact@v4 + with: + name: ios-artifact-dsym + path: ${{ steps.build.outputs.DSYM_PATH }} + + - name: Upload iOS sourcemaps + uses: actions/upload-artifact@v4 + with: + name: ios-artifact-sourcemaps + path: ./main.jsbundle.map diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 60bb97b0c2e7..057fdbfd6256 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -199,112 +199,90 @@ jobs: name: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && 'desktop-build-artifact' || 'desktop-staging-build-artifact' }} path: ./desktop-build/NewExpensify.dmg - iOS: - name: Build and deploy iOS + buildIOS: + name: Build iOS app + uses: ./.github/workflows/buildIOS.yml + if: ${{ github.ref == 'refs/heads/staging' }} needs: prep - env: - DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer - runs-on: macos-13-xlarge + secrets: inherit + with: + type: release + ref: staging + + uploadIOS: + name: Upload iOS App to TestFlight + needs: buildIOS + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Configure MapBox SDK - run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - - - name: Setup Node - id: setup-node - uses: ./.github/actions/composite/setupNode - - name: Setup Ruby uses: ruby/setup-ruby@v1.190.0 with: bundler-cache: true - - name: Cache Pod dependencies - uses: actions/cache@v4 - id: pods-cache - with: - path: ios/Pods - key: ${{ runner.os }}-pods-cache-${{ hashFiles('ios/Podfile.lock', 'firebase.json') }} - - - name: Compare Podfile.lock and Manifest.lock - id: compare-podfile-and-manifest - run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('ios/Podfile.lock') == hashFiles('ios/Pods/Manifest.lock') }}" >> "$GITHUB_OUTPUT" - - - name: Install cocoapods - uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 - if: steps.pods-cache.outputs.cache-hit != 'true' || steps.compare-podfile-and-manifest.outputs.IS_PODFILE_SAME_AS_MANIFEST != 'true' || steps.setup-node.outputs.cache-hit != 'true' - with: - timeout_minutes: 10 - max_attempts: 5 - command: scripts/pod-install.sh - - - name: Decrypt AppStore profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore.mobileprovision NewApp_AppStore.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt AppStore Notification Service profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AppStore_Notification_Service.mobileprovision NewApp_AppStore_Notification_Service.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - name: Decrypt App Store Connect API key run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output ios-fastlane-json-key.json ios-fastlane-json-key.json.gpg env: LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - name: Get iOS native version - id: getIOSVersion - run: echo "IOS_VERSION=$(echo '${{ needs.prep.outputs.APP_VERSION }}' | tr '-' '.')" >> "$GITHUB_OUTPUT" + - name: Download iOS build artifacts + uses: actions/download-artifact@v4 + with: + path: /tmp/artifacts + pattern: ios-artifact-* + merge-multiple: true - - name: Build iOS release app - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios build + - name: Log downloaded artifact paths + run: ls -R /tmp/artifacts - - name: Upload release build to TestFlight - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + - name: Upload iOS app to TestFlight run: bundle exec fastlane ios upload_testflight env: APPLE_CONTACT_EMAIL: ${{ secrets.APPLE_CONTACT_EMAIL }} APPLE_CONTACT_PHONE: ${{ secrets.APPLE_CONTACT_PHONE }} APPLE_DEMO_EMAIL: ${{ secrets.APPLE_DEMO_EMAIL }} APPLE_DEMO_PASSWORD: ${{ secrets.APPLE_DEMO_PASSWORD }} - - - name: Submit build for App Store review - if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: bundle exec fastlane ios submit_for_review - env: - VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }} + ipaPath: /tmp/artifacts/${{ needs.buildIOS.outputs.IPA_FILE_NAME }} + dsymPath: /tmp/artifacts/${{ needs.buildIOS.outputs.DSYM_FILE_NAME }} - name: Upload iOS build to Browser Stack if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/Users/runner/work/App/App/New Expensify.ipa" + run: curl -u "$BROWSERSTACK" -X POST "https://api-cloud.browserstack.com/app-live/upload" -F "file=@/tmp/artifacts/${{ needs.buildIOS.outputs.IPA_FILE_NAME }}" env: BROWSERSTACK: ${{ secrets.BROWSERSTACK }} - - name: Upload iOS sourcemaps artifact - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - uses: actions/upload-artifact@v4 - with: - name: ios-sourcemaps-artifact - path: ./main.jsbundle.map + submitIOS: + name: Submit iOS app for Apple review + needs: prep + if: ${{ github.ref == 'refs/heads/production' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Upload iOS build artifact - if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - uses: actions/upload-artifact@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1.190.0 with: - name: ios-build-artifact - path: /Users/runner/work/App/App/New\ Expensify.ipa + bundler-cache: true + + - name: Decrypt App Store Connect API key + run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output ios-fastlane-json-key.json ios-fastlane-json-key.json.gpg + env: + LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + + - name: Get iOS native version + id: getIOSVersion + run: echo "IOS_VERSION=$(echo '${{ needs.prep.outputs.APP_VERSION }}' | tr '-' '.')" >> "$GITHUB_OUTPUT" + + - name: Submit build for App Store review + run: bundle exec fastlane ios submit_for_review + env: + VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }} - name: Warn deployers if iOS production deploy failed - if: ${{ failure() && fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} + if: ${{ failure() }} uses: 8398a7/action-slack@v3 with: status: custom @@ -407,7 +385,7 @@ jobs: name: Post a Slack message when any platform fails to build or deploy runs-on: ubuntu-latest if: ${{ failure() }} - needs: [buildAndroid, uploadAndroid, submitAndroid, desktop, iOS, web] + needs: [buildAndroid, uploadAndroid, submitAndroid, desktop, buildIOS, uploadIOS, submitIOS, web] steps: - name: Checkout uses: actions/checkout@v4 @@ -433,7 +411,7 @@ jobs: outputs: IS_AT_LEAST_ONE_PLATFORM_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAtLeastOnePlatform.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED }} IS_ALL_PLATFORMS_DEPLOYED: ${{ steps.checkDeploymentSuccessOnAllPlatforms.outputs.IS_ALL_PLATFORMS_DEPLOYED }} - needs: [buildAndroid, uploadAndroid, submitAndroid, desktop, iOS, web] + needs: [buildAndroid, uploadAndroid, submitAndroid, desktop, buildIOS, uploadIOS, submitIOS, web] if: ${{ always() }} steps: - name: Check deployment success on at least one platform @@ -441,18 +419,19 @@ jobs: run: | isAtLeastOnePlatformDeployed="false" if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" == "success" ]; then + if [ '${{ needs.submitAndroid.result }}' == 'success' ] || \ + [ '${{ needs.submitIOS.result }}' == 'success' ]; then isAtLeastOnePlatformDeployed="true" fi else - if [ "${{ needs.uploadAndroid.result }}" == "success" ]; then + if [ '${{ needs.uploadAndroid.result }}' == 'success' ] || \ + [ '${{ needs.uploadIOS.result }}' == 'success' ]; then isAtLeastOnePlatformDeployed="true" fi fi - - if [ "${{ needs.iOS.result }}" == "success" ] || \ - [ "${{ needs.desktop.result }}" == "success" ] || \ - [ "${{ needs.web.result }}" == "success" ]; then + + if [ '${{ needs.desktop.result }}' == 'success' ] || \ + [ '${{ needs.web.result }}' == 'success' ]; then isAtLeastOnePlatformDeployed="true" fi echo "IS_AT_LEAST_ONE_PLATFORM_DEPLOYED=$isAtLeastOnePlatformDeployed" >> "$GITHUB_OUTPUT" @@ -461,19 +440,20 @@ jobs: - name: Check deployment success on all platforms id: checkDeploymentSuccessOnAllPlatforms run: | - isAllPlatformsDeployed="false" - if [ "${{ needs.iOS.result }}" == "success" ] && \ - [ "${{ needs.desktop.result }}" == "success" ] && \ - [ "${{ needs.web.result }}" == "success" ]; then + isAllPlatformsDeployed='false' + if [ '${{ needs.desktop.result }}' == 'success' ] && \ + [ '${{ needs.web.result }}' == 'success' ]; then isAllPlatformsDeployed="true" fi if [ ${{ github.ref }} == 'refs/heads/production' ]; then - if [ "${{ needs.submitAndroid.result }}" != "success" ]; then + if [ '${{ needs.submitAndroid.result }}' != 'success' ] || \ + [ '${{ needs.submitIOS.result }}' != 'success' ]; then isAllPlatformsDeployed="false" fi else - if [ "${{ needs.uploadAndroid.result }}" != "success" ]; then + if [ '${{ needs.uploadAndroid.result }}' != 'success' ] || \ + [ '${{ needs.uploadIOS.result }}' != 'success' ]; then isAllPlatformsDeployed="false" fi fi @@ -484,7 +464,7 @@ jobs: createPrerelease: runs-on: ubuntu-latest if: ${{ always() && github.ref == 'refs/heads/staging' && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} - needs: [prep, checkDeploymentSuccess] + needs: [prep, checkDeploymentSuccess, buildAndroid, buildIOS] steps: - name: Download all workflow run artifacts uses: actions/download-artifact@v4 @@ -511,12 +491,12 @@ jobs: continue-on-error: true run: | gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \ - ./android-sourcemaps-artifact/index.android.bundle.map#android-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ - ./android-build-artifact/app-production-release.aab \ + ./android-artifact-sourcemaps/index.android.bundle.map#android-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ + ./android-artifact-aab/${{ needs.buildAndroid.outputs.AAB_FILE_NAME }} \ ./desktop-staging-sourcemaps-artifact/desktop-staging-merged-source-map.js.map#desktop-staging-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ ./desktop-staging-build-artifact/NewExpensify.dmg#NewExpensifyStaging.dmg \ - ./ios-sourcemaps-artifact/main.jsbundle.map#ios-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ - ./ios-build-artifact/New\ Expensify.ipa \ + ./ios-artifact-sourcemaps/main.jsbundle.map#ios-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ + ./ios-artifact-ipa/${{ needs.buildIOS.outputs.IPA_FILE_NAME }} \ ./web-staging-sourcemaps-artifact/web-staging-merged-source-map.js.map#web-staging-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \ ./web-staging-build-tar-gz-artifact/webBuild.tar.gz#stagingWebBuild.tar.gz \ ./web-staging-build-zip-artifact/webBuild.zip#stagingWebBuild.zip @@ -597,7 +577,7 @@ jobs: name: Post a Slack message when all platforms deploy successfully runs-on: ubuntu-latest if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, submitAndroid, desktop, buildIOS, uploadIOS, submitIOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] steps: - name: 'Announces the deploy in the #announce Slack room' uses: 8398a7/action-slack@v3 @@ -651,11 +631,11 @@ jobs: postGithubComments: uses: ./.github/workflows/postDeployComments.yml if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} - needs: [prep, buildAndroid, uploadAndroid, submitAndroid, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] + needs: [prep, buildAndroid, uploadAndroid, submitAndroid, buildIOS, uploadIOS, submitIOS, desktop, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] with: version: ${{ needs.prep.outputs.APP_VERSION }} env: ${{ github.ref == 'refs/heads/production' && 'production' || 'staging' }} android: ${{ github.ref == 'refs/heads/production' && needs.submitAndroid.result || needs.uploadAndroid.result }} - ios: ${{ needs.iOS.result }} + ios: ${{ github.ref == 'refs/heads/production' && needs.submitIOS.result || needs.uploadIOS.result }} web: ${{ needs.web.result }} desktop: ${{ needs.desktop.result }} diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 672d468ed3b1..ac20a8d09141 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -120,73 +120,41 @@ jobs: # $s3APKPath is set from within the Fastfile, android upload_s3 lane echo "S3_APK_PATH=$s3APKPath" >> "$GITHUB_OUTPUT" - iOS: - name: Build and deploy iOS for testing - needs: [validateActor, getBranchRef] + buildIOS: + name: Build iOS app for testing + uses: ./.github/workflows/buildIOS.yml if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - env: - DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer - runs-on: macos-13-xlarge + needs: [validateActor, getBranchRef] + secrets: inherit + with: + type: adhoc + ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + pull_request_number: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} + + uploadIOS: + name: Upload IOS app to S3 + needs: buildIOS + runs-on: ubuntu-latest + outputs: + S3_IPA_PATH: ${{ steps.exportS3Path.outputs.S3_IPA_PATH }} steps: - name: Checkout uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} - - - name: Configure MapBox SDK - run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} - - - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it - run: | - cp .env.staging .env.adhoc - sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc - echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc - - - name: Setup Node - id: setup-node - uses: ./.github/actions/composite/setupNode - - - name: Setup XCode - run: sudo xcode-select -switch /Applications/Xcode_15.2.0.app - name: Setup Ruby uses: ruby/setup-ruby@v1.190.0 with: bundler-cache: true - - name: Cache Pod dependencies - uses: actions/cache@v4 - id: pods-cache - with: - path: ios/Pods - key: ${{ runner.os }}-pods-cache-${{ hashFiles('ios/Podfile.lock', 'firebase.json') }} - - - name: Compare Podfile.lock and Manifest.lock - id: compare-podfile-and-manifest - run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('ios/Podfile.lock') == hashFiles('ios/Pods/Manifest.lock') }}" >> "$GITHUB_OUTPUT" - - - name: Install cocoapods - uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 - if: steps.pods-cache.outputs.cache-hit != 'true' || steps.compare-podfile-and-manifest.outputs.IS_PODFILE_SAME_AS_MANIFEST != 'true' || steps.setup-node.outputs.cache-hit != 'true' + - name: Download IOS build artifacts + uses: actions/download-artifact@v4 with: - timeout_minutes: 10 - max_attempts: 5 - command: scripts/pod-install.sh - - - name: Decrypt AdHoc profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc.mobileprovision NewApp_AdHoc.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - - - name: Decrypt AdHoc Notification Service profile - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output NewApp_AdHoc_Notification_Service.mobileprovision NewApp_AdHoc_Notification_Service.mobileprovision.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + path: /tmp/artifacts + pattern: ios-artifact-* + merge-multiple: true - - name: Decrypt certificate - run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} + - name: Log downloaded artifact paths + run: ls -R /tmp/artifacts - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 @@ -195,22 +163,20 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - - name: Build AdHoc app - run: bundle exec fastlane ios build_adhoc - - name: Upload AdHoc build to S3 run: bundle exec fastlane ios upload_s3 env: + ipaPath: /tmp/artifacts/${{ needs.buildIOS.outputs.IPA_FILE_NAME }} S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} S3_BUCKET: ad-hoc-expensify-cash S3_REGION: us-east-1 - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: ios - path: ./ios_paths.json + - name: Export S3 paths + id: exportS3Path + run: | + # $s3IpaPath is set from within the Fastfile, ios upload_s3 lane + echo "S3_IPA_PATH=$s3IpaPath" >> "$GITHUB_OUTPUT" desktop: name: Build and deploy Desktop for testing @@ -291,41 +257,27 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, uploadAndroid, iOS, desktop, web] - if: ${{ always() }} + needs: [validateActor, getBranchRef, uploadAndroid, uploadIOS, desktop, web] + if: ${{ always() && fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} steps: - name: Checkout uses: actions/checkout@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} with: ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} - name: Download Artifact uses: actions/download-artifact@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - - - name: Read JSONs with iOS paths - id: get_ios_path - if: ${{ needs.iOS.result == 'success' }} - run: | - content_ios="$(cat ./ios/ios_paths.json)" - content_ios="${content_ios//'%'/'%25'}" - content_ios="${content_ios//$'\n'/'%0A'}" - content_ios="${content_ios//$'\r'/'%0D'}" - ios_path=$(echo "$content_ios" | jq -r '.html_path') - echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT" - name: Publish links to apps for download - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} uses: ./.github/actions/javascript/postTestBuildComment with: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} GITHUB_TOKEN: ${{ github.token }} ANDROID: ${{ needs.uploadAndroid.result }} DESKTOP: ${{ needs.desktop.result }} - IOS: ${{ needs.iOS.result }} + IOS: ${{ needs.uploadIOS.result }} WEB: ${{ needs.web.result }} ANDROID_LINK: ${{ needs.uploadAndroid.outputs.S3_APK_PATH }} DESKTOP_LINK: https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/${{ env.PULL_REQUEST_NUMBER }}/NewExpensify.dmg - IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }} + IOS_LINK: ${{ needs.uploadIOS.outputs.S3_IPA_PATH }} WEB_LINK: https://${{ env.PULL_REQUEST_NUMBER }}.pr-testing.expensify.com diff --git a/fastlane/Fastfile b/fastlane/Fastfile index eed84acdc916..1a7499a2a2c3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,6 +19,7 @@ KEY_GRADLE_APK_PATH = "apkPath" KEY_S3_APK_PATH = "s3APKPath" KEY_GRADLE_AAB_PATH = "aabPath" KEY_IPA_PATH = "ipaPath" +KEY_S3_IPA_PATH = "s3IpaPath" KEY_DSYM_PATH = "dsymPath" # Export environment variables to GITHUB_ENV @@ -215,7 +216,7 @@ platform :ios do build_app( workspace: "./ios/NewExpensify.xcworkspace", scheme: "New Expensify", - output_name: "New Expensify.ipa", + output_name: "NewExpensify.ipa", export_options: { provisioningProfiles: { "com.chat.expensify.chat" => "(NewApp) AppStore", @@ -256,6 +257,7 @@ platform :ios do workspace: "./ios/NewExpensify.xcworkspace", skip_profile_detection: true, scheme: "New Expensify AdHoc", + output_name: "NewExpensify_AdHoc.ipa", export_method: "ad-hoc", export_options: { method: "ad-hoc", @@ -280,12 +282,16 @@ platform :ios do ipa: ENV[KEY_IPA_PATH], app_directory: "ios/#{ENV['PULL_REQUEST_NUMBER']}", ) - sh("echo '{\"ipa_path\": \"#{lane_context[SharedValues::S3_IPA_OUTPUT_PATH]}\",\"html_path\": \"#{lane_context[SharedValues::S3_HTML_OUTPUT_PATH]}\"}' > ../ios_paths.json") + puts "Saving S3 outputs in env..." + exportEnvVars({ + KEY_S3_IPA_PATH => lane_context[SharedValues::S3_HTML_OUTPUT_PATH], + }) end desc "Upload app to TestFlight" lane :upload_testflight do upload_to_testflight( + ipa: ENV[KEY_IPA_PATH], api_key_path: "./ios/ios-fastlane-json-key.json", distribute_external: true, notify_external_testers: true,