Skip to content

iOS - PR Checks

iOS - PR Checks #9

Workflow file for this run

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 }}