Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions .github/actions/save-macos-caches/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Save macOS build caches
description: Save the caches that setup-macos restored without an exact match. Run it at the end of the job, after the build input times are saved.

# The repository has a 10 GB cache quota, and the large entries (SwiftPM, CLI and App build
# state) change with every source change. Only main saves them, so that pull requests do not
# evict the entries of main, which all pull requests restore. The small Mise and GhosttyKit
# entries change rarely, and a GhosttyKit miss is slow to rebuild, so pull requests save them.
runs:
using: composite
steps:
- if: env.PROWL_MISE_CACHE_HIT != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.local/share/mise
~/.cache/mise
key: ${{ env.PROWL_MISE_CACHE_KEY }}
- if: env.PROWL_GHOSTTY_CACHE_HIT != 'true'
uses: actions/cache/save@v4
with:
path: |
App/Frameworks/GhosttyKit.xcframework
App/Resources/ghostty
App/Resources/terminfo
.ghostty_hash
.ghostty_build_stamp
key: ${{ env.PROWL_GHOSTTY_CACHE_KEY }}
- if: env.PROWL_SPM_CACHE_HIT != 'true' && github.ref == 'refs/heads/main'
uses: actions/cache/save@v4
with:
path: ~/Library/Caches/prowl-spm-cache/SourcePackages
key: ${{ env.PROWL_SPM_CACHE_KEY }}
- if: env.PROWL_CLI_CACHE_HIT != 'true' && github.ref == 'refs/heads/main'
uses: actions/cache/save@v4
with:
path: |
CLI/.build
Mirror/Relay/.build
key: ${{ env.PROWL_CLI_CACHE_KEY }}
- if: env.PROWL_APP_CACHE_HIT != 'true' && github.ref == 'refs/heads/main'
uses: actions/cache/save@v4
with:
path: ${{ env.PROWL_DERIVED_DATA_PATH }}
key: ${{ env.PROWL_APP_CACHE_KEY }}
66 changes: 47 additions & 19 deletions .github/actions/setup-macos/action.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: Setup macOS build deps
description: Install + cache mise tools and GhosttyKit build outputs
description: Install mise tools and restore the build caches. Run save-macos-caches at the end of the job to save them.

runs:
using: composite
steps:
- name: Mise cache
uses: actions/cache@v4
id: mise_cache
uses: actions/cache/restore@v4
with:
path: |
~/.local/share/mise
Expand All @@ -31,7 +32,8 @@ runs:
echo "Xcode cache identity: $XCODE_CACHE_ID"

- name: CLI SwiftPM build cache
uses: actions/cache@v4
id: cli_cache
uses: actions/cache/restore@v4
with:
path: |
CLI/.build
Expand All @@ -53,7 +55,7 @@ runs:
printf '%s\n' "GHOSTTY_SHA=$GHOSTTY_SHA" >> "$GITHUB_ENV"
- name: Ghostty cache
id: ghostty_cache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: |
App/Frameworks/GhosttyKit.xcframework
Expand All @@ -76,26 +78,14 @@ runs:
touch .ghostty_build_stamp

- name: SPM cache
uses: actions/cache@v4
id: spm_cache
uses: actions/cache/restore@v4
with:
path: ~/Library/Caches/prowl-spm-cache/SourcePackages
key: ${{ runner.os }}-${{ runner.arch }}-spm-v1-${{ hashFiles('.package.resolved') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-spm-v1-

- name: Xcode compilation cache
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData/CompilationCache.noindex
# GitHub cache entries are immutable. A source-state suffix guarantees that a
# successful changed build saves newly compiled objects; restore-keys select the
# newest compatible CAS. Never restore across Xcode builds because compiler cache
# keys and storage formats are toolchain-specific.
key: ${{ runner.os }}-${{ runner.arch }}-xcode-compilation-cache-v3-${{ env.XCODE_CACHE_ID }}-${{ hashFiles('.package.resolved', 'Tuist.swift', 'Workspace.swift', 'App/Project.swift', 'App/Config/**', 'mise.toml', 'Makefile', '.github/actions/setup-macos/action.yml', '.github/workflows/test.yml') }}-${{ hashFiles('App/**/*.swift', 'Shared/Sources/**/*.swift', 'Mirror/Shared/*.swift', 'Mirror/Relay/Sources/**/*.swift') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-xcode-compilation-cache-v3-${{ env.XCODE_CACHE_ID }}-${{ hashFiles('.package.resolved', 'Tuist.swift', 'Workspace.swift', 'App/Project.swift', 'App/Config/**', 'mise.toml', 'Makefile', '.github/actions/setup-macos/action.yml', '.github/workflows/test.yml') }}-
${{ runner.os }}-${{ runner.arch }}-xcode-compilation-cache-v3-${{ env.XCODE_CACHE_ID }}-

- name: App incremental cache identity
shell: bash
run: |
Expand All @@ -105,14 +95,52 @@ runs:
APP_WORKSPACE_ID="$(pwd -P | shasum -a 256 | cut -d ' ' -f 1)"
printf '%s\n' "APP_INPUT_PATHS_ID=$APP_INPUT_PATHS_ID" "APP_WORKSPACE_ID=$APP_WORKSPACE_ID" >> "$GITHUB_ENV"
- name: App incremental build cache
uses: actions/cache@v4
id: app_cache
uses: actions/cache/restore@v4
with:
path: ${{ env.PROWL_DERIVED_DATA_PATH }}
# Incremental state needs a stricter boundary than the content-addressed cache.
# Reuse across source edits, but not changed paths, build settings or toolchains.
key: ${{ runner.os }}-${{ runner.arch }}-app-incremental-v2-${{ env.XCODE_CACHE_ID }}-${{ env.APP_WORKSPACE_ID }}-${{ env.GHOSTTY_SHA }}-${{ env.APP_INPUT_PATHS_ID }}-${{ hashFiles('CLI/Package.swift', 'CLI/Package.resolved', 'Shared/Package.swift', 'Mirror/Relay/Package.swift', '.package.resolved', 'Tuist.swift', 'Workspace.swift', 'App/Project.swift', 'App/Config/**', 'mise.toml', 'Makefile', '.github/actions/setup-macos/action.yml', '.github/workflows/test.yml', 'scripts/ci-source-mtimes.py') }}-${{ hashFiles('App/**/*.swift', 'Shared/Sources/**/*.swift', 'Mirror/Shared/*.swift', 'Mirror/Relay/Sources/**/*.swift') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-app-incremental-v2-${{ env.XCODE_CACHE_ID }}-${{ env.APP_WORKSPACE_ID }}-${{ env.GHOSTTY_SHA }}-${{ env.APP_INPUT_PATHS_ID }}-${{ hashFiles('CLI/Package.swift', 'CLI/Package.resolved', 'Shared/Package.swift', 'Mirror/Relay/Package.swift', '.package.resolved', 'Tuist.swift', 'Workspace.swift', 'App/Project.swift', 'App/Config/**', 'mise.toml', 'Makefile', '.github/actions/setup-macos/action.yml', '.github/workflows/test.yml', 'scripts/ci-source-mtimes.py') }}-
# Xcode keeps the compilation cache (CAS) in the DerivedData folder, so the App incremental
# cache carries it. When no compatible incremental state exists (changed file roster or build
# configuration), keep only the CAS of the newest App build for this Xcode. The build is then
# clean, but it replays compiler outputs from the content-addressed cache.
- name: Compilation cache of an earlier App build
id: app_cas
if: steps.app_cache.outputs.cache-matched-key == ''
uses: actions/cache/restore@v4
with:
path: ${{ env.PROWL_DERIVED_DATA_PATH }}
key: ${{ runner.os }}-${{ runner.arch }}-app-incremental-v2-${{ env.XCODE_CACHE_ID }}-${{ env.APP_WORKSPACE_ID }}-
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-app-incremental-v2-${{ env.XCODE_CACHE_ID }}-${{ env.APP_WORKSPACE_ID }}-
- name: Keep only the compilation cache
if: steps.app_cas.outputs.cache-matched-key != ''
shell: bash
run: |
set -euo pipefail
find "$PROWL_DERIVED_DATA_PATH" -mindepth 1 -maxdepth 1 ! -name CompilationCache.noindex -exec rm -rf {} +
du -sh "$PROWL_DERIVED_DATA_PATH/CompilationCache.noindex" 2>/dev/null || echo "No compilation cache in the restored state"
- name: Restore unchanged App source times
shell: bash
run: python3 scripts/ci-source-mtimes.py restore --scope app --manifest "$PROWL_DERIVED_DATA_PATH/source-times.json"

- name: Cache keys for save-macos-caches
shell: bash
run: |
set -euo pipefail
{
echo "PROWL_MISE_CACHE_KEY=${{ steps.mise_cache.outputs.cache-primary-key }}"
echo "PROWL_MISE_CACHE_HIT=${{ steps.mise_cache.outputs.cache-hit }}"
echo "PROWL_CLI_CACHE_KEY=${{ steps.cli_cache.outputs.cache-primary-key }}"
echo "PROWL_CLI_CACHE_HIT=${{ steps.cli_cache.outputs.cache-hit }}"
echo "PROWL_GHOSTTY_CACHE_KEY=${{ steps.ghostty_cache.outputs.cache-primary-key }}"
echo "PROWL_GHOSTTY_CACHE_HIT=${{ steps.ghostty_cache.outputs.cache-hit }}"
echo "PROWL_SPM_CACHE_KEY=${{ steps.spm_cache.outputs.cache-primary-key }}"
echo "PROWL_SPM_CACHE_HIT=${{ steps.spm_cache.outputs.cache-hit }}"
echo "PROWL_APP_CACHE_KEY=${{ steps.app_cache.outputs.cache-primary-key }}"
echo "PROWL_APP_CACHE_HIT=${{ steps.app_cache.outputs.cache-hit }}"
} >> "$GITHUB_ENV"
44 changes: 44 additions & 0 deletions .github/workflows/mirror-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: mirror-android

# The Android mirror client needs only a minimal check, so CI runs it only when the client
# changes and for each published release.
on:
push:
branches:
- main
paths:
- Mirror/Android/**
- .github/workflows/mirror-android.yml
pull_request:
paths:
- Mirror/Android/**
- .github/workflows/mirror-android.yml
# Release tags are lightweight, so `git push --follow-tags` does not push them and
# `gh release create` makes them on GitHub. That sends no tag push event.
release:
types:
- published

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
mirror-android:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '17'
cache: gradle
- uses: android-actions/setup-android@v4
- name: Install Android SDK 34
run: sdkmanager 'platforms;android-34' 'build-tools;34.0.0'
- name: Build and test Android mirror
working-directory: Mirror/Android
run: ./gradlew --no-daemon :app:assembleDebug :app:testDebugUnitTest :app:lintDebug
64 changes: 64 additions & 0 deletions .github/workflows/mirror-ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: mirror-ios

# The iOS mirror client needs only a minimal check, so CI runs its unit tests only when the
# client changes and for each published release. The UI suite runs locally with
# `make test-mirror-ios`.
on:
push:
branches:
- main
paths:
- Mirror/iOS/**
- Mirror/Shared/**
- scripts/select_ios_simulator.py
- .github/workflows/mirror-ios.yml
pull_request:
paths:
- Mirror/iOS/**
- Mirror/Shared/**
- scripts/select_ios_simulator.py
- .github/workflows/mirror-ios.yml
# Release tags are lightweight, so `git push --follow-tags` does not push them and
# `gh release create` makes them on GitHub. That sends no tag push event.
release:
types:
- published

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# The project needs no submodule, GhosttyKit, CLI, or Swift package, so this job does not use
# setup-macos. It also must not save the setup-macos caches, or it would take the cache keys
# of the Mac app job.
mirror-ios:
runs-on: macos-26
timeout-minutes: 20
permissions:
contents: read
env:
MISE_HTTP_TIMEOUT: 120
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Install only the tools that the iOS mirror targets use.
MISE_EXEC_AUTO_INSTALL: false
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@v6
- name: Install Tuist and xcsift
run: |
brew install mise
mise install tuist github:ldomaradzki/xcsift
- name: Test iOS mirror
run: make test-mirror-ios-unit
- name: Upload xcresult bundle and log (on failure)
# A job that reaches its timeout is cancelled, and its partial results are still useful.
if: failure() || cancelled()
uses: actions/upload-artifact@v4
with:
name: mirror-ios-xcresult-${{ github.run_id }}-${{ github.run_attempt }}
path: |
build/test-results/mirror-ios-unit.xcresult
build/test-results/mirror-ios-unit.xcresult.log
if-no-files-found: ignore
retention-days: 7
Loading
Loading