Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/actions/compute-e2e-build-fingerprint/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Compute E2E build fingerprint
description: Compute a content-based fingerprint for E2E builds to enable caching

inputs:
platform:
description: 'Platform to compute fingerprint for (ios, android-detox, android-maestro)'
required: true

outputs:
fingerprint:
description: 'Computed fingerprint'
value: ${{ steps.compute.outputs.fingerprint }}

runs:
using: composite
steps:
- name: Compute fingerprint
id: compute
shell: bash
run: |
# Fingerprint = sha256 of git blob hashes for all binary-impacting paths.
# Missing a path causes false cache HITs (wrong binary tested).
# Test files (detox/e2e/**, detox/maestro/**) are excluded so test-only
# PRs still hit cache. Keep app-source subset in sync with label-manager filter.
case "${{ inputs.platform }}" in
ios)
paths=(app/ ios/ libraries/ share_extension/ patches/ \
types/ assets/fonts/ assets/sounds/ \
assets/base/images/ assets/base/release/ assets/base/config.json \
package.json package-lock.json \
tsconfig.json babel.config.js metro.config.js \
index.tsx app.json react-native.config.js \
.nvmrc fastlane/)
;;
android-detox)
paths=(app/ android/ libraries/ share_extension/ patches/ \
types/ assets/fonts/ assets/sounds/ \
assets/base/images/ assets/base/release/ assets/base/config.json \
package.json package-lock.json \
tsconfig.json babel.config.js metro.config.js \
index.tsx app.json react-native.config.js \
.nvmrc detox/.detoxrc.json detox/package.json \
detox/package-lock.json detox/scripts/)
;;
android-maestro)
paths=(app/ android/ libraries/ share_extension/ patches/ \
types/ assets/fonts/ assets/sounds/ \
assets/base/images/ assets/base/release/ assets/base/config.json \
package.json package-lock.json \
tsconfig.json babel.config.js metro.config.js \
index.tsx app.json react-native.config.js \
.nvmrc)
;;
*)
echo "::error::Invalid platform: ${{ inputs.platform }}"
exit 1
;;
esac

# Fall back to commit SHA if ls-tree fails (e.g., shallow clone) so the build still runs.
fingerprint=$(git ls-tree -r HEAD -- "${paths[@]}" 2>/dev/null | sha256sum | cut -d' ' -f1)
if [ -z "$fingerprint" ] || [ "$fingerprint" = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ]; then
fingerprint="nocache-$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
echo "::warning::git ls-tree produced no output — using fallback fingerprint. S3 cache will miss."
fi
echo "fingerprint=${fingerprint}" >> "$GITHUB_OUTPUT"
echo "Computed fingerprint for ${{ inputs.platform }}: ${fingerprint}"
11 changes: 10 additions & 1 deletion .github/actions/prepare-ios-build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,22 @@ runs:
restore-keys: |
${{ runner.os }}-pods-v4-intune-${{ inputs.intune-enabled }}-${{ steps.intune-hash.outputs.hash }}-

- name: Cache CocoaPods Ruby gems
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: vendor/bundle
key: ${{ runner.os }}-cocoapods-gems-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-cocoapods-gems-

- name: ci/install-pods-dependencies
shell: bash
env:
INTUNE_ENABLED: ${{ inputs.intune-enabled }}
run: |
echo "::group::install-pods-dependencies"
echo "INTUNE_ENABLED=$INTUNE_ENABLED"
npm run ios-gems
bundle config set --local path vendor/bundle
bundle install
npm run pod-install
echo "::endgroup::"
20 changes: 5 additions & 15 deletions .github/actions/prepare-mobile-build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,13 @@ runs:
using: composite
steps:
# The required ruby version is mentioned in '.ruby-version'
# bundler-cache restores/installs fastlane/Gemfile.lock gems before the job
# continues. The previous manual cache targeted repo-root vendor/bundle while
# bundle install ran in fastlane/, so the cache never hit or saved.
- uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0

- name: ci/setup-fastlane-dependencies
shell: bash
run: |
echo "::group::setup-fastlane-dependencies"
bundle install
echo "::endgroup::"
working-directory: ./fastlane

- name: Cache Ruby gems
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
bundler-cache: true
working-directory: fastlane

- name: ci/prepare-node-deps
uses: ./.github/actions/prepare-node-deps
104 changes: 104 additions & 0 deletions .github/actions/s3-build-cache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: S3 Build Cache
description: >-
Cache/restore compiled app binaries in S3 keyed by content fingerprint.
Uses S3 instead of actions/cache because large binaries (200MB–1GB)
blow the 10GB GHA cache limit and cause eviction thrashing.

inputs:
fingerprint:
description: 'Content fingerprint (from compute-e2e-build-fingerprint)'
required: true
platform:
description: 'Platform key: ios, android-detox, android-maestro'
required: true
bucket:
description: 'S3 bucket name'
required: true
s3-key-prefix:
description: 'Key prefix in the bucket (e.g. e2e-builds/)'
required: false
default: 'e2e-builds/'
cache-path:
description: 'Local path to cache (glob for restore, directory for save)'
required: true
mode:
description: >-
restore (check + download), save (upload after build), or
check (existence only — no download; for cache-warming jobs)
required: true

outputs:
cache-hit:
description: 'true if a cached build was found and restored'
value: ${{ steps.s3-cache.outputs.cache-hit }}

runs:
using: composite
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
aws-access-key-id: ${{ env.DETOX_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ env.DETOX_AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1

- name: S3 cache ${{ inputs.mode }}
id: s3-cache
shell: bash
env:
FINGERPRINT: ${{ inputs.fingerprint }}
PLATFORM: ${{ inputs.platform }}
BUCKET: ${{ inputs.bucket }}
KEY_PREFIX: ${{ inputs.s3-key-prefix }}
CACHE_PATH: ${{ inputs.cache-path }}
MODE: ${{ inputs.mode }}
run: |
set -eu
S3_KEY="${KEY_PREFIX}${PLATFORM}/${FINGERPRINT}.tar.gz"

if [ "$MODE" = "restore" ] || [ "$MODE" = "check" ]; then
echo "Checking s3://${BUCKET}/${S3_KEY} ..."
if aws s3 ls "s3://${BUCKET}/${S3_KEY}" 2>/dev/null; then
if [ "$MODE" = "check" ]; then
echo "Cache HIT — existence-only check, skipping download."
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
# Any download/extract failure must degrade to a cache miss so the
# build still runs instead of aborting the action under set -eu.
elif aws s3 cp "s3://${BUCKET}/${S3_KEY}" /tmp/build-cache.tar.gz \
&& tar xzf /tmp/build-cache.tar.gz; then
rm -f /tmp/build-cache.tar.gz
echo "Restored build from S3 (fingerprint=${FINGERPRINT:0:12})"
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Cache restore failed (download/extract error) — treating as cache miss."
rm -f /tmp/build-cache.tar.gz || true
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
fi
else
echo "Cache MISS — no cached build for fingerprint ${FINGERPRINT:0:12}"
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
elif [ "$MODE" = "save" ]; then
# nullglob: non-matching glob → empty array instead of literal pattern.
shopt -s nullglob
FILES=()
for f in $CACHE_PATH; do
[ -e "$f" ] && FILES+=("$f")
done
shopt -u nullglob

if [ ${#FILES[@]} -eq 0 ]; then
echo "::warning::No files found at '$CACHE_PATH' — skipping S3 cache save."
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Uploading ${#FILES[@]} path(s) to s3://${BUCKET}/${S3_KEY} ..."
tar czf /tmp/build-cache.tar.gz "${FILES[@]}"
aws s3 cp /tmp/build-cache.tar.gz "s3://${BUCKET}/${S3_KEY}"
rm -f /tmp/build-cache.tar.gz
echo "Saved build to S3 (fingerprint=${FINGERPRINT:0:12})"
else
echo "::error::Invalid mode: $MODE (expected restore, save, or check)"
exit 1
fi
2 changes: 1 addition & 1 deletion .github/workflows/cmt-provisioner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ permissions: {}

jobs:
signal:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Signal matterwick
run: echo "CMT provisioning requested — matterwick will handle provisioning and dispatch."
13 changes: 9 additions & 4 deletions .github/workflows/compatibility-matrix-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# CMT runs against a freshly-cut build-release-NNN branch, so the app content
# never matches main's fingerprint — the S3 build cache would always miss.
# CMT's optimization is structural instead: each app (iOS, Android Detox,
# Android Maestro) is built ONCE below and reused across every server-version
# leg of the matrix via github.run_id artifacts.
jobs:
calculate-commit-hash:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
outputs:
MOBILE_SHA: ${{ steps.repo.outputs.MOBILE_SHA }}
steps:
Expand All @@ -32,7 +37,7 @@ jobs:
run: echo "MOBILE_SHA=$(git rev-parse HEAD)" >> ${GITHUB_OUTPUT}

update-initial-status:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
contents: read
statuses: write
Expand All @@ -55,7 +60,7 @@ jobs:

# Maestro only runs against the latest server version; matrix filtering happens here since job-level if: can't access matrix.
prepare-maestro-matrix:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.filter.outputs.matrix }}
steps:
Expand Down Expand Up @@ -295,7 +300,7 @@ jobs:
record_tests_in_zephyr: true

update-final-status:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
if: always()
permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/e2e-android-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ env:

jobs:
generate-specs:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
outputs:
specs: ${{ steps.generate-specs.outputs.specs }}
build_id: ${{ steps.resolve-device.outputs.BUILD_ID }}
Expand Down Expand Up @@ -385,7 +385,7 @@ jobs:

generate-report:
if: always()
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- generate-specs
- e2e-android
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-cancel-on-label-removal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
|| github.event.label.name == 'E2E/Run-iOS'
|| github.event.label.name == 'E2E/Run-Android')
&& github.event.sender.login != 'mattermost-build'
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
actions: write
statuses: write
Expand Down
Loading
Loading