iOS - PR Checks #9
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: iOS - PR Checks | |
| defaults: | |
| run: | |
| working-directory: iOS | |
| on: | |
| push: | |
| branches: [ main, "release/ios/**" ] | |
| paths: | |
| - 'SharedPackages/**' | |
| - 'iOS/**' | |
| pull_request: | |
| paths: | |
| - '.github/**' | |
| - '.xcode-version' | |
| - 'SharedPackages/**' | |
| - 'iOS/**' | |
| schedule: | |
| - cron: '10,35,50 4,5,6 * * *' | |
| workflow_call: | |
| inputs: | |
| branch: | |
| description: "Branch name" | |
| required: false | |
| type: string | |
| skip-release: | |
| description: "Skip release build" | |
| default: false | |
| type: boolean | |
| skip-asana: | |
| description: "Skip Asana task creation on tests failure" | |
| type: boolean | |
| default: true | |
| secrets: | |
| APPLE_API_KEY_BASE64: | |
| required: true | |
| APPLE_API_KEY_ID: | |
| required: true | |
| APPLE_API_KEY_ISSUER: | |
| required: true | |
| ASANA_ACCESS_TOKEN: | |
| required: true | |
| GH_RO_PAT: | |
| required: true | |
| MATCH_PASSWORD: | |
| required: true | |
| SSH_PRIVATE_KEY_FASTLANE_MATCH: | |
| required: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| shellcheck: | |
| name: ShellCheck | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out the code | |
| if: github.event_name == 'pull_request' || github.event_name == 'push' | |
| uses: actions/checkout@v4 | |
| - name: Check out the code | |
| if: github.event_name != 'pull_request' && github.event_name != 'push' | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.branch || github.ref_name }} | |
| - name: Run ShellCheck | |
| uses: ludeeus/action-shellcheck@master | |
| with: | |
| format: gcc | |
| scandir: iOS/scripts | |
| env: | |
| SHELLCHECK_OPTS: -x -P iOS/scripts | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: macos-15 | |
| timeout-minutes: 60 # temporary (vs original 20) to fix the issue with GHA slowness | |
| env: | |
| RUNS_ON: macos-15 # keep this in sync with runs-on above | |
| WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }} | |
| outputs: | |
| commit_author: ${{ steps.fetch_commit_author.outputs.commit_author }} | |
| pr_reviewers: ${{ steps.fetch_commit_author.outputs.pr_reviewers }} | |
| steps: | |
| - name: Check out the code | |
| if: github.event_name == 'pull_request' || github.event_name == 'push' | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Check out the code | |
| if: github.event_name != 'pull_request' && github.event_name != 'push' | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| ref: ${{ inputs.branch || github.ref_name }} | |
| - name: Start measuring duration | |
| id: start-duration-tests | |
| uses: ./.github/actions/measure-duration | |
| with: | |
| mode: start | |
| - name: Set cache key hash | |
| run: | | |
| has_only_tags=$(jq '[ .pins[].state | has("version") ] | all' DuckDuckGo-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) | |
| if [[ "$has_only_tags" == "true" ]]; then | |
| # hashFiles does not respect the working directory, so the full path must be specified | |
| echo "cache_key_hash=${{ hashFiles('iOS/DuckDuckGo-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}" >> $GITHUB_ENV | |
| else | |
| echo "Package.resolved contains dependencies specified by branch or commit, skipping cache." | |
| fi | |
| - name: Cache SPM | |
| if: env.cache_key_hash | |
| uses: actions/cache@v4 | |
| with: | |
| path: iOS/DerivedData/SourcePackages | |
| key: ${{ runner.os }}-ios-${{ env.cache_key_hash }} | |
| restore-keys: | | |
| ${{ runner.os }}-ios- | |
| - name: Select Xcode | |
| uses: ./.github/actions/select-xcode-version | |
| - name: Select iOS Simulator | |
| id: select-simulator | |
| uses: ./.github/actions/select-ios-simulator | |
| with: | |
| preferred-versions: "18.6,18.2" | |
| - name: Build and test | |
| id: run-unit-tests | |
| run: | | |
| echo "Using iOS simulator: ${{ steps.select-simulator.outputs.ios-version }}" | |
| echo "Destination: ${{ steps.select-simulator.outputs.destination }}" | |
| set -o pipefail && xcodebuild test \ | |
| -scheme "iOS Browser" \ | |
| -destination "${{ steps.select-simulator.outputs.destination }}" \ | |
| -derivedDataPath "DerivedData" \ | |
| -skipPackagePluginValidation \ | |
| -skipMacroValidation \ | |
| DDG_SLOW_COMPILE_CHECK_THRESHOLD=250 \ | |
| | tee xcodebuild.log \ | |
| | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml | |
| - name: Upload JUnit XML as artifact | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: ios-unittests.xml | |
| path: | | |
| iOS/unittests.xml | |
| retention-days: 7 | |
| - name: Upload logs if workflow failed | |
| uses: actions/upload-artifact@v4 | |
| if: failure() || cancelled() | |
| with: | |
| name: BuildLogs | |
| path: | | |
| iOS/xcodebuild.log | |
| iOS/DerivedData/Logs/Test/*.xcresult | |
| retention-days: 7 | |
| - name: Publish unit tests report | |
| uses: mikepenz/action-junit-report@v3 | |
| with: | |
| report_paths: iOS/unittests.xml | |
| - name: Update Asana with failed unit tests | |
| if: always() && steps.run-unit-tests.outcome == 'failure' | |
| env: | |
| ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
| run: | | |
| # Extract failed tests from the junit report | |
| # Only keep failures unique by classname and name (column 1 and 2 of the yq output) | |
| yq < unittests.xml -p xml -o json -r \ | |
| $'[.testsuites.testsuite[].testcase] | flatten | map(select(.failure) | .+@classname + " " + .+@name + " \'" + .failure.+@message + "\' ${{ env.WORKFLOW_URL }}") | .[]' \ | |
| | sort -u -k 1,2 \ | |
| | xargs -L 1 ${{ github.workspace }}/scripts/report-failed-unit-test.sh -s ${{ vars.APPLE_CI_FAILING_TESTS_IOS_FAILED_TESTS_SECTION_ID }} | |
| - name: Report job duration | |
| if: success() || (always() && steps.run-unit-tests.outcome == 'failure') | |
| uses: ./.github/actions/measure-duration | |
| with: | |
| mode: stop | |
| start-timestamp: ${{ steps.start-duration-tests.outputs.start-timestamp }} | |
| pixel-name: m_ci_apple_duration_ios_unit_tests | |
| pixel-params: | | |
| runner: ${{ env.RUNS_ON }} | |
| - name: Send Status Pixel | |
| if: always() && github.ref_name == 'main' | |
| uses: ./.github/actions/send-pixel | |
| with: | |
| pixel-name: m_ci_apple_status_ios_main_unit_tests_${{ steps.run-unit-tests.outcome }} | |
| - name: Fetch latest commit author and PR reviewers | |
| if: failure() && github.ref_name == 'main' && github.run_attempt == 1 && inputs.skip-asana != true | |
| id: fetch_commit_author | |
| uses: ./.github/actions/fetch-commit-info | |
| with: | |
| github-token: ${{ github.token }} | |
| release-build: | |
| name: Make Release Build | |
| # Dependabot doesn't have access to all secrets, so we skip this job, also skip for releases | |
| if: github.actor != 'dependabot[bot]' && inputs.skip-release != true | |
| runs-on: macos-15 | |
| timeout-minutes: 60 # temporary (vs original 20) to fix the issue with GHA slowness | |
| env: | |
| RUNS_ON: macos-15 # keep this in sync with runs-on above | |
| steps: | |
| - name: Register SSH keys for access to certificates | |
| uses: webfactory/ssh-agent@v0.9.1 | |
| with: | |
| ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} | |
| - name: Check out the code | |
| if: github.event_name == 'pull_request' || github.event_name == 'push' | |
| uses: actions/checkout@v4 | |
| - name: Check out the code | |
| if: github.event_name != 'pull_request' && github.event_name != 'push' | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.branch || github.ref_name }} | |
| - name: Start measuring duration | |
| id: start-duration-release-build | |
| uses: ./.github/actions/measure-duration | |
| with: | |
| mode: start | |
| - name: Set cache key hash | |
| run: | | |
| has_only_tags=$(jq '[ .pins[].state | has("version") ] | all' DuckDuckGo-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) | |
| if [[ "$has_only_tags" == "true" ]]; then | |
| echo "cache_key_hash=${{ hashFiles('iOS/DuckDuckGo-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}" >> $GITHUB_ENV | |
| else | |
| echo "Package.resolved contains dependencies specified by branch or commit, skipping cache." | |
| fi | |
| - name: Cache SPM | |
| if: env.cache_key_hash | |
| uses: actions/cache@v4 | |
| with: | |
| path: iOS/DerivedData/SourcePackages | |
| key: ${{ runner.os }}-ios-release-${{ env.cache_key_hash }} | |
| restore-keys: | | |
| ${{ runner.os }}-ios-release- | |
| - name: Select Xcode | |
| uses: ./.github/actions/select-xcode-version | |
| - name: Select iOS Simulator | |
| id: select-simulator | |
| uses: ./.github/actions/select-ios-simulator | |
| with: | |
| preferred-versions: "18.6,18.2" | |
| - name: Setup Ruby and dependencies | |
| uses: ./.github/actions/setup-ruby | |
| with: | |
| working-directory: iOS | |
| cache-key-prefix: ios-ruby | |
| - name: Build the app | |
| env: | |
| APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| run: | | |
| echo "Using iOS simulator: ${{ steps.select-simulator.outputs.ios-version }}" | |
| echo "Destination: ${{ steps.select-simulator.outputs.destination }}" | |
| bundle exec fastlane sync_signing | |
| set -o pipefail && xcodebuild \ | |
| -scheme "iOS Browser" \ | |
| -destination "${{ steps.select-simulator.outputs.destination }}" \ | |
| -derivedDataPath "DerivedData" \ | |
| -configuration "Release" \ | |
| -skipPackagePluginValidation \ | |
| -skipMacroValidation \ | |
| | xcbeautify | |
| - name: Report job duration | |
| uses: ./.github/actions/measure-duration | |
| with: | |
| mode: stop | |
| start-timestamp: ${{ steps.start-duration-release-build.outputs.start-timestamp }} | |
| pixel-name: m_ci_apple_duration_ios_release_build | |
| pixel-params: | | |
| runner: ${{ env.RUNS_ON }} | |
| create-asana-task: | |
| name: Create Asana Task | |
| needs: [unit-tests, shellcheck, release-build] | |
| if: failure() && github.ref_name == 'main' && github.run_attempt == 1 && inputs.skip-asana != true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out the code | |
| uses: actions/checkout@v4 | |
| - name: Create Asana Task | |
| uses: ./.github/actions/asana-failed-pr-checks | |
| with: | |
| action: create-task | |
| asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
| asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_IOS_POST_MERGE_SECTION_ID }} | |
| commit-author: ${{ needs.unit-tests.outputs.commit_author }} | |
| pr-reviewers: ${{ needs.unit-tests.outputs.pr_reviewers }} | |
| github-token: ${{ secrets.GH_RO_PAT }} | |
| close-asana-task: | |
| name: Close Asana Task | |
| needs: [unit-tests, shellcheck, release-build] | |
| if: success() && github.ref_name == 'main' && github.run_attempt > 1 && inputs.skip-asana != true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out the code | |
| uses: actions/checkout@v4 | |
| - name: Close Asana Task | |
| uses: ./.github/actions/asana-failed-pr-checks | |
| with: | |
| action: close-task | |
| asana-access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
| asana-section-id: ${{ vars.APPLE_CI_FAILING_TESTS_IOS_POST_MERGE_SECTION_ID }} | |
| github-token: ${{ secrets.GH_RO_PAT }} |