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
167 changes: 167 additions & 0 deletions .github/workflows/deploy-ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: deploy-ios

on:
workflow_call:
inputs:
project-name:
description: 'Name of the application to build (e.g. qTox).'
required: false
type: string
cmake-args:
description: 'Arguments to pass to CMake.'
required: false
type: string
need-qt:
description: |
Whether the project needs Qt (built from source); default is
true. Set to false if the project doesn't need Qt to save time.
required: false
type: boolean
default: true

jobs:
build:
name: Build
strategy:
matrix:
arch: [arm64, x86_64, arm64-iphonesimulator]
ios: ["15.0"]
exclude:
- arch: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'packaging') && 'x86_64' }}
- arch: ${{ github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'packaging') && 'arm64-iphonesimulator' }}
runs-on: macos-14
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Checkout ci-tools
if: github.repository != 'TokTok/ci-tools'
uses: actions/checkout@v4
with:
repository: TokTok/ci-tools
path: third_party/ci-tools
submodules: true
- name: Link ci-tools
if: github.repository == 'TokTok/ci-tools'
run: ln -s .. third_party/ci-tools
- name: Checkout dockerfiles
uses: actions/checkout@v4
with:
repository: TokTok/dockerfiles
path: third_party/dockerfiles
submodules: true

- name: Determine artifact file name
id: artifact
run: |
PROJECT_NAME="${{ inputs.project-name }}"
if [ -z "$PROJECT_NAME" ]; then
PROJECT_NAME="$(pcre2grep -M -o1 'project\(\s*(\S+)' CMakeLists.txt)"
fi
echo "project-name=$PROJECT_NAME" >>$GITHUB_OUTPUT

ARTIFACT="$PROJECT_NAME-${{ matrix.arch }}-${{ matrix.ios }}.ipa"
echo "artifact=$ARTIFACT" >>$GITHUB_OUTPUT
echo "artifact-ref=$PROJECT_NAME-${{ github.sha }}-ios-${{ matrix.ios }}-${{ matrix.arch }}" >>$GITHUB_OUTPUT

if [ "${{ matrix.arch }}" = "x86_64" ]; then
echo "qt_arch=iphonesimulator-x86_64" >>$GITHUB_OUTPUT
elif [ "${{ matrix.arch }}" = "arm64-iphonesimulator" ]; then
echo "qt_arch=iphonesimulator-arm64" >>$GITHUB_OUTPUT
else
echo "qt_arch=ios-arm64" >>$GITHUB_OUTPUT
fi
- name: Download Qt
if: inputs.need-qt
run: |
third_party/dockerfiles/qtox/deps/download_qt.sh \
--arch ${{ steps.artifact.outputs.qt_arch }} \
--dep-prefix /Users/runner/work/deps
mkdir -p /Users/runner/work/host-qt
third_party/dockerfiles/qtox/deps/download_qt.sh \
--arch $(uname -m) \
--macos-version 12.0 \
--dep-prefix /Users/runner/work/host-qt
- name: Cache dependencies (except Qt)
id: cache-deps
uses: actions/cache@v4
with:
path: |
/Users/runner/work/deps/bin
/Users/runner/work/deps/include
/Users/runner/work/deps/lib
/Users/runner/work/deps/share
key: ${{ github.job }}-ios-distributable-${{ matrix.arch }}-${{ matrix.ios }}-deps
- name: Homebrew dependencies to build dependencies
run: brew bundle --file platform/ios/Brewfile-static
- name: Build dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: third_party/dockerfiles/qtox/deps/local_install_deps.sh
--arch ${{ steps.artifact.outputs.qt_arch }}
--dep-file platform/deps.depfile
--dep-prefix /Users/runner/work/deps
- name: Cache compiler output
uses: actions/cache@v4
with:
path: .cache/ccache
key: ${{ github.job }}-ios-distributable-${{ matrix.arch }}-${{ matrix.ios }}-ccache
- name: Set up ccache
run: ccache
--set-config=max_size=200M
--set-config=cache_dir="$PWD/.cache/ccache" && ccache --show-config
- name: Build application bundle
run: third_party/ci-tools/platform/ios/build.sh
--project-name ${{ steps.artifact.outputs.project-name }}
--arch "${{ matrix.arch }}"
--ios-version "${{ matrix.ios }}"
--dep-prefix /Users/runner/work/deps
--host-path /Users/runner/work/host-qt/qt
--
${{ inputs.cmake-args }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact.outputs.artifact-ref }}
path: |
${{ steps.artifact.outputs.artifact }}
${{ steps.artifact.outputs.artifact }}.sha256
if-no-files-found: error
- name: Get tag name for release file name
if: contains(github.ref, 'refs/tags/v')
id: get_version
run: |
VERSION="$(echo "$GITHUB_REF" | cut -d / -f 3)"
echo "version_tag=$VERSION" >>$GITHUB_OUTPUT
echo "release_artifact=${{ steps.artifact.outputs.project-name }}-$VERSION.${{ matrix.arch }}-${{ matrix.ios }}.ipa" >>$GITHUB_OUTPUT
- name: Rename artifact for release upload
if: contains(github.ref, 'refs/tags/v')
run: |
cp "${{ steps.artifact.outputs.artifact }}" "${{ steps.get_version.outputs.release_artifact }}"
cp "${{ steps.artifact.outputs.artifact }}.sha256" "${{ steps.get_version.outputs.release_artifact }}.sha256"
- name: Upload to versioned release
if: contains(github.ref, 'refs/tags/v')
uses: ncipollo/release-action@v1
with:
allowUpdates: true
draft: true
artifacts: "${{ steps.get_version.outputs.release_artifact }},${{ steps.get_version.outputs.release_artifact }}.sha256"
- name: Rename artifact for nightly upload
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: |
cp "${{ steps.artifact.outputs.artifact }}" ${{ steps.artifact.outputs.project-name }}-nightly-${{ matrix.arch }}-${{ matrix.ios }}.ipa
cp "${{ steps.artifact.outputs.artifact }}.sha256" ${{ steps.artifact.outputs.project-name }}-nightly-${{ matrix.arch }}-${{ matrix.ios }}.ipa.sha256
- name: Upload to nightly release
uses: ncipollo/release-action@v1
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
with:
allowUpdates: true
tag: nightly
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
prerelease: true
replacesArtifacts: true
artifacts: "${{ steps.artifact.outputs.project-name }}-nightly-${{ matrix.arch }}-${{ matrix.ios }}.ipa,${{ steps.artifact.outputs.project-name }}-nightly-${{ matrix.arch }}-${{ matrix.ios }}.ipa.sha256"
5 changes: 5 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ jobs:
smoke-test: test/smoke-test.sh
test-files: test/smoke-test.sh

ios:
name: iOS
uses: ./.github/workflows/deploy-ios.yml
needs: [prepare]

windows:
name: Windows
uses: ./.github/workflows/deploy-windows.yml
Expand Down
10 changes: 10 additions & 0 deletions platform/ios/Brewfile-static
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Dependencies for building all dependencies from source as static libraries.
# Needed for distributable builds.
brew "bash" # macOS bash is too old
brew "cmake"
brew "git"
brew "ninja"
brew "pcre2" # for pcre2grep
brew "zip"
# accelerate builds with ccache
brew "ccache"
38 changes: 38 additions & 0 deletions platform/ios/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>CiTools</string>
<key>CFBundleExecutable</key>
<string>citools</string>
<key>CFBundleIdentifier</key>
<string>chat.tox.citools</string>
<key>CFBundleName</key>
<string>CiTools</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.1</string>
<key>CFBundleSignature</key>
<string>toxcitools</string>
<key>CFBundleVersion</key>
<string>0.0.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>15.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
144 changes: 144 additions & 0 deletions platform/ios/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/bin/bash

# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright © 2026 The TokTok team

# Fail out on error
set -eux -o pipefail

IOS_ARCH="arm64"
IOS_VERSION="15.0"
IOS_PLATFORM="iphoneos"
HOST_PATH=""

GIT_ROOT=$(git rev-parse --show-toplevel)
DEP_PREFIX="$GIT_ROOT/third_party/deps"

usage() {
echo "Usage: $0 --project-name <project-name> [options]"
echo "Options:"
echo " --project-name <project-name> Name of the project (required)"
echo " --dep-prefix <dep-prefix> Dependency prefix (default: third_party/deps)"
echo " --arch <arch> Architecture (arm64 or x86_64)"
echo " --ios-version <ios-version> iOS version (default: 15.0)"
echo " --host-path <host-path> Host Qt path (optional)"
echo " --help, -h Show this help message"
}

while (($# > 0)); do
case $1 in
--project-name)
PROJECT_NAME=$2
shift 2
;;
--dep-prefix)
DEP_PREFIX=$2
shift 2
;;
--arch)
IOS_ARCH=$2
shift 2
;;
--ios-version)
IOS_VERSION=$2
shift 2
;;
--host-path)
HOST_PATH=$2
shift 2
;;
--help | -h)
usage
exit 1
;;
--)
shift
break
;;
*)
echo "Unexpected argument $1"
usage
exit 1
;;
esac
done

if [ -z "${PROJECT_NAME+x}" ]; then
echo "--project-name is a required argument"
usage
exit 1
fi

if [[ "$IOS_ARCH" == *"iphonesimulator"* ]] || [[ "$IOS_ARCH" == "x86_64" ]]; then
IOS_PLATFORM="iphonesimulator"
fi

# Extract the base architecture (e.g., "arm64" from "arm64-iphonesimulator")
CMAKE_ARCH="${IOS_ARCH%-iphonesimulator}"

readonly BIN_NAME="$PROJECT_NAME-$IOS_ARCH-$IOS_VERSION.ipa"
CMAKE="$DEP_PREFIX/qt/bin/qt-cmake"
PREFIX_PATH="$DEP_PREFIX"

# Build project.
ccache --zero-stats

# We use the Xcode generator for iOS to ensure proper bundle structure.
# CODE_SIGNING_ALLOWED=NO allows us to build in CI without a developer cert.
# ONLY_ACTIVE_ARCH=NO ensures we build for the specified architecture.
EXTRA_CMAKE_ARGS=()
if [ -f "platform/ios/Info.plist" ]; then
EXTRA_CMAKE_ARGS+=("-DCMAKE_MACOSX_BUNDLE_INFO_PLIST=$(realpath platform/ios/Info.plist)")
fi

if [ -n "$HOST_PATH" ]; then
EXTRA_CMAKE_ARGS+=("-DQT_HOST_PATH=$HOST_PATH")
fi

"$CMAKE" \
-DCMAKE_CXX_FLAGS="-isystem/usr/local/include" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT="$IOS_PLATFORM" \
-DCMAKE_OSX_ARCHITECTURES="$CMAKE_ARCH" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="$IOS_VERSION" \
-DCMAKE_PREFIX_PATH="$PREFIX_PATH" \
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY="" \
-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO \
-DCMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT="dwarf" \
-DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
"${EXTRA_CMAKE_ARGS[@]}" \
-G Xcode \
-B_build \
"$@" \
.

cmake --build _build --config Release

# Packaging into IPA
mkdir -p _build/Payload
# Search for the .app bundle. Xcode output paths can vary depending on project structure.
APP_BUNDLE=$(find "_build/Release-$IOS_PLATFORM" -name "*.app" -type d | head -n 1)
if [ -z "$APP_BUNDLE" ]; then
echo "Could not find .app bundle in _build/Release-$IOS_PLATFORM"
exit 1
fi

cp -r "$APP_BUNDLE" _build/Payload/
pushd _build
zip -r "../$BIN_NAME" Payload
popd

ccache --show-stats

# Check if the binary exists.
if [[ ! -s "$BIN_NAME" ]]; then
echo "There's no $BIN_NAME!"
exit 1
fi

# Create a sha256 checksum.
shasum -a 256 "$BIN_NAME" >"$BIN_NAME".sha256
Loading