mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-11 20:27:39 +00:00
ci: build the core library and the app for the iOS simulator, run a UI smoke test
Adds the ios-sim workflow, triggered only by pushes to f/ci-ios-sim and by workflow_dispatch. The core job builds lib:simplex-chat with cabal using the flake's iOS settings (swift JSON, client library, static roll-up archive, threaded RTS, CommonCrypto), packages it as the Hydra zip, and patches it for the simulator with scripts/ios/prepare.sh and mac2ios. The app job builds "SimpleX (iOS)" for the arm64 simulator against it and drives onboarding, the chat list and the search field with AXe, saving screenshots and accessibility trees. This first run tests the whole pipeline, mainly whether the core library builds and links.
This commit is contained in:
1 parent
e7678b634c
commit
bcef840e99
4 files changed
+366
No files matched your search
@@ -0,0 +1,161 @@
|
||||
name: ios-sim
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- f/ci-ios-sim
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ios-sim-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GHC_VER: 9.6.3
|
||||
CABAL_VER: 3.10.2.0
|
||||
CORE_PKG: pkg-ios-aarch64-swift-json.zip
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
|
||||
jobs:
|
||||
core:
|
||||
name: core library for the iOS simulator
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Show runner
|
||||
run: |
|
||||
sw_vers
|
||||
sysctl -n hw.ncpu hw.memsize
|
||||
df -h /
|
||||
xcodebuild -version
|
||||
|
||||
- name: Restore core library
|
||||
id: core-cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/Downloads/${{ env.CORE_PKG }}
|
||||
key: ios-sim-core-${{ hashFiles('src/**', 'cabal.project', 'simplex-chat.cabal', 'scripts/ci/ios-sim/build-core.sh') }}
|
||||
|
||||
- name: Setup Haskell
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
uses: simplex-chat/setup-haskell-action@v2
|
||||
with:
|
||||
ghc-version: ${{ env.GHC_VER }}
|
||||
cabal-version: ${{ env.CABAL_VER }}
|
||||
|
||||
- name: Restore cabal store
|
||||
id: cabal-cache
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/store
|
||||
dist-newstyle
|
||||
key: ios-sim-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
ios-sim-ghc${{ env.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}-
|
||||
ios-sim-ghc${{ env.GHC_VER }}-
|
||||
macos-latest-ghc${{ env.GHC_VER }}-
|
||||
|
||||
- name: Install libraries
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
run: brew install openssl@3 libffi gmp
|
||||
|
||||
- name: Build core library
|
||||
id: build
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
run: scripts/ci/ios-sim/build-core.sh ~/Downloads/${{ env.CORE_PKG }}
|
||||
|
||||
- name: Save cabal store
|
||||
if: ${{ !cancelled() && steps.core-cache.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/store
|
||||
dist-newstyle
|
||||
key: ${{ steps.cabal-cache.outputs.cache-primary-key }}
|
||||
|
||||
- name: Save core library
|
||||
if: steps.core-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/Downloads/${{ env.CORE_PKG }}
|
||||
key: ${{ steps.core-cache.outputs.cache-primary-key }}
|
||||
|
||||
- name: Patch core library for the simulator
|
||||
run: scripts/ci/ios-sim/prepare-libs.sh
|
||||
|
||||
- name: Upload core library
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-core-aarch64
|
||||
path: ~/Downloads/${{ env.CORE_PKG }}
|
||||
compression-level: 0
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
app:
|
||||
name: app in the iOS simulator
|
||||
needs: core
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Show runner
|
||||
run: |
|
||||
sw_vers
|
||||
df -h /
|
||||
xcodebuild -version
|
||||
xcrun simctl list runtimes
|
||||
|
||||
- name: Download core library
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ios-core-aarch64
|
||||
path: ~/Downloads
|
||||
|
||||
- name: Prepare libraries
|
||||
run: |
|
||||
scripts/ci/ios-sim/prepare-libs.sh
|
||||
scripts/ios/update-pbxproj.sh
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
xcodebuild build -quiet \
|
||||
-project apps/ios/SimpleX.xcodeproj \
|
||||
-scheme "SimpleX (iOS)" \
|
||||
-configuration Debug \
|
||||
-destination 'generic/platform=iOS Simulator' \
|
||||
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
|
||||
-onlyUsePackageVersionsFromResolvedFile \
|
||||
-skipPackagePluginValidation \
|
||||
-skipMacroValidation \
|
||||
ARCHS=arm64 \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
CODE_SIGN_IDENTITY=- \
|
||||
DEVELOPMENT_TEAM= \
|
||||
PROVISIONING_PROFILE_SPECIFIER=
|
||||
|
||||
- name: Install AXe
|
||||
run: brew install cameroncooke/axe/axe
|
||||
|
||||
- name: Run smoke test
|
||||
run: scripts/ci/ios-sim/smoke-test.sh "$RUNNER_TEMP/DerivedData/Build/Products/Debug-iphonesimulator/SimpleX.app" "$RUNNER_TEMP/smoke-test"
|
||||
|
||||
- name: Upload smoke test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-sim-smoke-test
|
||||
path: ${{ runner.temp }}/smoke-test
|
||||
retention-days: 3
|
||||
if-no-files-found: warn
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Builds the iOS core library package with cabal, as flake.nix builds aarch64-darwin-ios:lib:simplex-chat.
|
||||
# Usage: scripts/ci/ios-sim/build-core.sh <output zip>
|
||||
# The zip has the same files as the Hydra package that scripts/ios/prepare.sh expects.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p "$(dirname "$1")"
|
||||
out="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
|
||||
ghc_ver=$(ghc --numeric-version)
|
||||
version=$(sed -n 's/^version: *//p' simplex-chat.cabal)
|
||||
openssl_dir=$(brew --prefix openssl@3)
|
||||
ffi_dir=$(brew --prefix libffi)
|
||||
gmp_dir=$(brew --prefix gmp)
|
||||
|
||||
ghc --version
|
||||
cabal --version
|
||||
echo "rts extra-libraries: $(ghc-pkg field rts extra-libraries --simple-output)"
|
||||
echo "ghc-bignum extra-libraries: $(ghc-pkg field ghc-bignum extra-libraries --simple-output)"
|
||||
find "$(ghc --print-libdir)" -name 'lib*ffi*.a' -o -name 'libgmp*.a' -o -name 'libHSrts*_thr.a'
|
||||
du -sh ~/.cabal/store dist-newstyle || true
|
||||
|
||||
cat > cabal.project.local <<EOF
|
||||
ignore-project: False
|
||||
|
||||
package simplex-chat
|
||||
flags: +swift +client_library
|
||||
static: True
|
||||
shared: False
|
||||
ghc-options: -threaded -DIOS
|
||||
|
||||
package simplexmq
|
||||
flags: +swift +client_library +commoncrypto
|
||||
extra-include-dirs: $openssl_dir/include
|
||||
extra-lib-dirs: $openssl_dir/lib
|
||||
|
||||
package direct-sqlcipher
|
||||
flags: +commoncrypto
|
||||
|
||||
package entropy
|
||||
flags: +DoNotGetEntropy
|
||||
EOF
|
||||
|
||||
cabal build -j lib:simplex-chat
|
||||
|
||||
build_dir=dist-newstyle/build/aarch64-osx/ghc-$ghc_ver/simplex-chat-$version/build
|
||||
ls -la "$build_dir"/libHSsimplex-chat-*
|
||||
|
||||
ghc_lib=$(ls "$build_dir"/libHSsimplex-chat-*-ghc"$ghc_ver".a)
|
||||
echo "Selected symbols defined in $ghc_lib:"
|
||||
{ nm -gU "$ghc_lib" 2>/dev/null || true; } \
|
||||
| awk '$NF ~ /^_(ffi_call|__gmpz_init|hs_init_with_rtsopts|chat_migrate_init_key|EVP_CIPHER_CTX_new|sqlite3_key)$/ {print " " $NF}' \
|
||||
| sort -u
|
||||
|
||||
pkg=$(mktemp -d)
|
||||
cp "$build_dir"/libHSsimplex-chat-*.a "$ffi_dir"/lib/libffi.a "$gmp_dir"/lib/libgmp.a "$gmp_dir"/lib/libgmpxx.a "$pkg"
|
||||
chmod u+w "$pkg"/*.a
|
||||
for f in "$pkg"/libHSsimplex-chat-*.a; do
|
||||
strip -x "$f"
|
||||
done
|
||||
ls -la "$pkg"
|
||||
|
||||
rm -f "$out"
|
||||
(cd "$pkg" && zip -9 "$out" *.a)
|
||||
ls -la "$out"
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Builds mac2ios, runs scripts/ios/prepare.sh on ~/Downloads/pkg-ios-aarch64-swift-json.zip,
|
||||
# and fails unless every object in apps/ios/Libraries/sim is marked for the iOS simulator.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mac2ios_rev=4dcb77d5ea896d749381806dfab5358851b08951
|
||||
tools=$(mktemp -d)
|
||||
for f in mac2ios.c ar.h macho.h; do
|
||||
curl --proto '=https' --tlsv1.2 -sSfL -o "$tools/$f" "https://raw.githubusercontent.com/zw3rk/mobile-core-tools/$mac2ios_rev/$f"
|
||||
done
|
||||
cc -O2 -o "$tools/mac2ios" "$tools/mac2ios.c"
|
||||
|
||||
PATH="$tools:$PATH" scripts/ios/prepare.sh
|
||||
|
||||
for f in apps/ios/Libraries/sim/*.a; do
|
||||
echo "$f: $(du -h "$f" | cut -f1), $(lipo -info "$f")"
|
||||
platforms=$({ otool -l "$f" || true; } | awk '$1 == "platform" {print $2}' | sort | uniq -c)
|
||||
echo "${platforms:- no LC_BUILD_VERSION found}"
|
||||
if echo "$platforms" | awk 'NF {print $2}' | grep -vE '^(7|IOSSIMULATOR)$' > /dev/null; then
|
||||
echo "error: $f has objects for a platform other than the iOS simulator"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Installs the app in a new iPhone simulator and drives it with AXe: onboarding, chat list, typing in the search field.
|
||||
# Usage: scripts/ci/ios-sim/smoke-test.sh <path to SimpleX.app> <output dir>
|
||||
# Each step saves a screenshot and an accessibility tree to the output dir and prints the tree to the log.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
app=$1
|
||||
out=$2
|
||||
bundle_id=chat.simplex.app
|
||||
search_text="ci smoke"
|
||||
mkdir -p "$out"
|
||||
|
||||
axe --version
|
||||
runtime=com.apple.CoreSimulator.SimRuntime.iOS-$(xcrun --sdk iphonesimulator --show-sdk-version | tr . -)
|
||||
udid=$(xcrun simctl create ci-iphone com.apple.CoreSimulator.SimDeviceType.iPhone-17 "$runtime")
|
||||
echo "simulator $udid, runtime $runtime"
|
||||
xcrun simctl boot "$udid"
|
||||
xcrun simctl bootstatus "$udid" -b
|
||||
open -a Simulator --args -CurrentDeviceUDID "$udid"
|
||||
|
||||
xcrun simctl spawn "$udid" log stream --level debug --style compact --predicate 'process == "SimpleX"' > "$out/app.log" 2>&1 &
|
||||
log_pid=$!
|
||||
|
||||
step=0
|
||||
snap() {
|
||||
step=$((step + 1))
|
||||
local f
|
||||
f=$(printf '%s/%02d-%s' "$out" "$step" "$1")
|
||||
xcrun simctl io "$udid" screenshot "$f.png" > /dev/null
|
||||
axe describe-ui --udid "$udid" > "$f.json"
|
||||
echo "::group::accessibility tree: $1"
|
||||
cat "$f.json"
|
||||
echo "::endgroup::"
|
||||
}
|
||||
|
||||
wait_for() {
|
||||
local deadline=$((SECONDS + $2))
|
||||
local tree="$out/wait.json"
|
||||
until axe describe-ui --udid "$udid" > "$tree" && grep -qF "$1" "$tree"; do
|
||||
if [ "$SECONDS" -ge "$deadline" ]; then
|
||||
echo "error: \"$1\" not shown after $2 s"
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
tap_label() {
|
||||
axe tap --label "$1" --wait-timeout "$2" --post-delay 2 --udid "$udid"
|
||||
}
|
||||
|
||||
finish() {
|
||||
local status=$?
|
||||
snap final || true
|
||||
kill "$log_pid" || true
|
||||
echo "::group::core start in the app log"
|
||||
grep -E 'chatMigrateInit|startChat' "$out/app.log" || true
|
||||
echo "::endgroup::"
|
||||
echo "::group::app log, last 300 lines"
|
||||
tail -n 300 "$out/app.log"
|
||||
echo "::endgroup::"
|
||||
find ~/Library/Logs/DiagnosticReports -name 'SimpleX*' -newer "$app" 2>/dev/null | while read -r report; do
|
||||
echo "::group::crash report $report"
|
||||
head -n 200 "$report"
|
||||
echo "::endgroup::"
|
||||
done || true
|
||||
exit "$status"
|
||||
}
|
||||
trap finish EXIT
|
||||
|
||||
xcrun simctl install "$udid" "$app"
|
||||
xcrun simctl launch "$udid" "$bundle_id"
|
||||
|
||||
wait_for "Get started" 180
|
||||
snap first-screen
|
||||
tap_label "Get started" 10
|
||||
|
||||
wait_for "Create profile" 30
|
||||
tap_label "Enter profile name..." 10 || echo "warning: name field not found by label, typing into the focused field"
|
||||
axe type 'citester' --udid "$udid"
|
||||
snap profile-name
|
||||
tap_label "Create profile" 10
|
||||
|
||||
wait_for "Continue" 60
|
||||
snap your-network
|
||||
tap_label "Continue" 10
|
||||
|
||||
wait_for "Accept" 30
|
||||
snap network-commitments
|
||||
tap_label "Accept" 10
|
||||
|
||||
sleep 5
|
||||
snap after-onboarding
|
||||
tap_label "Allow" 20 || echo "no notifications alert to allow"
|
||||
|
||||
wait_for "Search or paste SimpleX link" 60
|
||||
snap chat-list
|
||||
tap_label "Search or paste SimpleX link" 10 || axe tap --value "Search or paste SimpleX link" --post-delay 2 --udid "$udid"
|
||||
axe type "$search_text" --udid "$udid"
|
||||
sleep 2
|
||||
snap search-typed
|
||||
|
||||
tree=$(ls "$out"/*-search-typed.json)
|
||||
if ! grep -qF "$search_text" "$tree"; then
|
||||
echo "error: \"$search_text\" is not in the accessibility tree after typing"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -qF "Search in Directory" "$tree"; then
|
||||
echo "error: no \"Search in Directory\" row after typing"
|
||||
exit 1
|
||||
fi
|
||||
echo "smoke test passed: the chat list search field shows \"$search_text\""
|
||||
Reference in new issue
Block a user