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
276 changes: 202 additions & 74 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,109 +1,237 @@
name: CI
# Build Telegram-iOS (Bazel) on GitHub-hosted macOS runners.
#
# Optional repository secrets (override the committed appstore-configuration.json):
# TELEGRAM_API_ID / TELEGRAM_API_HASH – from https://my.telegram.org
# TELEGRAM_BUNDLE_ID / TELEGRAM_TEAM_ID
#
# If secrets are unset, CI uses build-system/appstore-configuration.json (same as
# the previous open-source workflow). Artifacts: Telegram.ipa + Telegram.DSYMs.zip.

name: Build iOS

on:
push:
branches: [ master ]

branches: [master]
pull_request:
workflow_dispatch:
inputs:
configuration:
description: Bazel build configuration
type: choice
options:
- release_arm64
- debug_arm64
default: release_arm64
create_release:
description: Publish a GitHub Release with the IPA
type: boolean
default: false

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

permissions:
contents: write

jobs:
build:
runs-on: macos-13
name: Build (${{ github.event.inputs.configuration || 'release_arm64' }})
runs-on: macos-26
timeout-minutes: 480

env:
BUILD_CONFIGURATION: ${{ github.event.inputs.configuration || 'release_arm64' }}
SOURCE_DIR: /Users/Shared/telegram-ios
BAZEL_USER_ROOT: /private/var/tmp/_bazel_telegram_ios
BAZEL_CACHE_DIR: /Users/Shared/telegram-bazel-cache

steps:
- uses: actions/checkout@v2
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
fetch-depth: '0'
fetch-depth: 0
submodules: false

- name: Set active Xcode path
# Relative submodule URLs (../rlottie.git, ../tgcalls.git) resolve under the
# fork owner and 404. Point them at the upstream TelegramMessenger repos.
- name: Fix submodule URLs for forks
run: |
XCODE_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["xcode"]);')
sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app/Contents/Developer
git config --file=.gitmodules submodule.submodules/rlottie/rlottie.url \
https://github.com/TelegramMessenger/rlottie.git
git config --file=.gitmodules submodule.submodules/TgVoipWebrtc/tgcalls.url \
https://github.com/TelegramMessenger/tgcalls.git
git submodule sync --recursive

- name: Create canonical source directory
- name: Checkout submodules
run: |
set -x
sudo mkdir -p /Users/Shared
cp -R $GITHUB_WORKSPACE /Users/Shared/
mv /Users/Shared/$(basename $GITHUB_WORKSPACE) /Users/Shared/telegram-ios
git submodule update --init --recursive --depth 1 --jobs 8

- name: Build the App
- name: Select Xcode
run: |
set -x

# source code paths are included in the final binary, so we need to make them stable across builds
SOURCE_DIR=/Users/Shared/telegram-ios

# use canonical bazel root
BAZEL_USER_ROOT="/private/var/tmp/_bazel_containerhost"
set -euo pipefail
XCODE_VERSION="$(python3 -c 'import json; print(json.load(open("versions.json"))["xcode"])')"
DEVELOPER_DIR="/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer"
if [ ! -d "$DEVELOPER_DIR" ]; then
echo "Xcode ${XCODE_VERSION} not found. Installed:"
ls /Applications | grep -i Xcode || true
# Prefer an exact match; otherwise use the newest installed 26.x.
CANDIDATE="$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V | tail -1 || true)"
if [ -z "$CANDIDATE" ]; then
echo "::error::No Xcode 26.x installation found on the runner."
exit 1
fi
DEVELOPER_DIR="${CANDIDATE}/Contents/Developer"
echo "::warning::versions.json requests Xcode ${XCODE_VERSION}; using $(basename "$CANDIDATE") instead."
fi
sudo xcode-select -s "$DEVELOPER_DIR"
xcodebuild -version
echo "DEVELOPER_DIR=$DEVELOPER_DIR" >> "$GITHUB_ENV"

- name: Free disk space
run: |
set -euo pipefail
df -h /
# Drop unused simulator runtimes / large caches that Bazel does not need.
sudo rm -rf \
/Users/runner/Library/Developer/CoreSimulator/Caches \
/Library/Developer/CoreSimulator/Profiles/Runtimes/* \
/Users/runner/.cargo \
/Users/runner/.rustup \
/usr/local/lib/android \
/Users/runner/Library/Android || true
df -h /

cd $SOURCE_DIR
- name: Create canonical source directory
run: |
set -euo pipefail
# Source paths are embedded in the binary; keep them stable across CI runs.
sudo mkdir -p /Users/Shared
sudo rm -rf "$SOURCE_DIR"
sudo cp -R "$GITHUB_WORKSPACE" "$SOURCE_DIR"
sudo chown -R "$(whoami)" "$SOURCE_DIR"

- name: Write build configuration
working-directory: ${{ env.SOURCE_DIR }}
env:
TELEGRAM_API_ID: ${{ secrets.TELEGRAM_API_ID }}
TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
TELEGRAM_BUNDLE_ID: ${{ secrets.TELEGRAM_BUNDLE_ID }}
TELEGRAM_TEAM_ID: ${{ secrets.TELEGRAM_TEAM_ID }}
run: |
set -euo pipefail
python3 <<'PY'
import json, os

path = "build-system/ci-configuration.json"
base_path = "build-system/appstore-configuration.json"
with open(base_path) as f:
cfg = json.load(f)

api_id = os.environ.get("TELEGRAM_API_ID") or ""
api_hash = os.environ.get("TELEGRAM_API_HASH") or ""
if api_id and api_hash:
cfg["api_id"] = api_id
cfg["api_hash"] = api_hash
print("Using TELEGRAM_API_* repository secrets")
else:
print(f"Secrets unset — using {base_path} (api_id={cfg.get('api_id')})")

if os.environ.get("TELEGRAM_BUNDLE_ID"):
cfg["bundle_id"] = os.environ["TELEGRAM_BUNDLE_ID"]
if "TELEGRAM_TEAM_ID" in os.environ and os.environ["TELEGRAM_TEAM_ID"] != "":
cfg["team_id"] = os.environ["TELEGRAM_TEAM_ID"]

with open(path, "w") as f:
json.dump(cfg, f, indent="\t")
f.write("\n")
print(f"Wrote {path} (api_id={cfg['api_id']}, bundle_id={cfg['bundle_id']})")
PY

- name: Compute build number
working-directory: ${{ env.SOURCE_DIR }}
run: |
set -euo pipefail
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
APP_VERSION="$(python3 -c 'import json; print(json.load(open("versions.json"))["app"])')"
COMMIT_COUNT="$(git rev-list --count HEAD)"
BUILD_NUMBER="$((COMMIT_COUNT + BUILD_NUMBER_OFFSET))"
{
echo "APP_VERSION=$APP_VERSION"
echo "BUILD_NUMBER=$BUILD_NUMBER"
} >> "$GITHUB_ENV"
echo "Telegram ${APP_VERSION} (${BUILD_NUMBER})"

- name: Build IPA
working-directory: ${{ env.SOURCE_DIR }}
run: |
set -euo pipefail
mkdir -p "$BAZEL_CACHE_DIR"

export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
export COMMIT_COUNT=$(git rev-list --count HEAD)
export COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
export BUILD_NUMBER="$COMMIT_COUNT"
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
python3 build-system/Make/ImportCertificates.py \
--path build-system/fake-codesigning/certs

python3 build-system/Make/ImportCertificates.py --path build-system/fake-codesigning/certs
python3 -u build-system/Make/Make.py \
--overrideXcodeVersion \
--bazelUserRoot="$BAZEL_USER_ROOT" \
--cacheDir="$BAZEL_CACHE_DIR" \
build \
--configurationPath="build-system/appstore-configuration.json" \
--configurationPath=build-system/ci-configuration.json \
--codesigningInformationPath=build-system/fake-codesigning \
--configuration=release_arm64 \
--configuration="$BUILD_CONFIGURATION" \
--buildNumber="$BUILD_NUMBER"

# collect ipa
- name: Collect artifacts
working-directory: ${{ env.SOURCE_DIR }}
run: |
set -euo pipefail
OUTPUT_PATH="build/artifacts"
rm -rf "$OUTPUT_PATH"
mkdir -p "$OUTPUT_PATH"
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa; do
cp "$f" $OUTPUT_PATH/
done

# collect dsym
IPA="$(find -L bazel-out -path '*/Telegram/Telegram.ipa' -type f 2>/dev/null | head -1 || true)"
if [ -z "$IPA" ]; then
echo "::error::Telegram.ipa not found under bazel-out"
find -L bazel-out -name '*.ipa' 2>/dev/null | head -50 || true
exit 1
fi
cp "$IPA" "$OUTPUT_PATH/Telegram.ipa"
echo "IPA=$IPA"

mkdir -p build/DSYMs
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM; do
cp -R "$f" build/DSYMs/
done
zip -r "./$OUTPUT_PATH/Telegram.DSYMs.zip" build/DSYMs 1>/dev/null

- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
while IFS= read -r dsym; do
cp -R "$dsym" build/DSYMs/
done < <(find -L bazel-out -path '*/Telegram/*.dSYM' -type d 2>/dev/null || true)
if [ -n "$(ls -A build/DSYMs 2>/dev/null || true)" ]; then
zip -r "$OUTPUT_PATH/Telegram.DSYMs.zip" build/DSYMs >/dev/null
else
echo "::warning::No dSYM bundles found"
fi

ls -lh "$OUTPUT_PATH"

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: Telegram-iOS-${{ env.APP_VERSION }}-${{ env.BUILD_NUMBER }}
path: |
${{ env.SOURCE_DIR }}/build/artifacts/Telegram.ipa
${{ env.SOURCE_DIR }}/build/artifacts/Telegram.DSYMs.zip
if-no-files-found: error
retention-days: 14

- name: Create GitHub Release
if: github.event_name == 'workflow_dispatch' && inputs.create_release == true
uses: softprops/action-gh-release@v2
with:
tag_name: build-${{ env.BUILD_NUMBER }}
release_name: Telegram ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
name: Telegram ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
body: |
An unsigned build of Telegram for iOS ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
draft: false
prerelease: false

- name: Upload Release IPA
id: upload-release-ipa
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: /Users/Shared/telegram-ios/build/artifacts/Telegram.ipa
asset_name: Telegram.ipa
asset_content_type: application/zip

- name: Upload Release DSYM
id: upload-release-dsym
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: /Users/Shared/telegram-ios/build/artifacts/Telegram.DSYMs.zip
asset_name: Telegram.DSYMs.zip
asset_content_type: application/zip
Unsigned (fake-codesigned) Telegram iOS build **${{ env.APP_VERSION }}** (${{ env.BUILD_NUMBER }}).
Configuration: `${{ env.BUILD_CONFIGURATION }}`.
Commit: `${{ github.sha }}`.
files: |
${{ env.SOURCE_DIR }}/build/artifacts/Telegram.ipa
${{ env.SOURCE_DIR }}/build/artifacts/Telegram.DSYMs.zip
fail_on_unmatched_files: false
generate_release_notes: true
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

[submodule "submodules/rlottie/rlottie"]
path = submodules/rlottie/rlottie
url=../rlottie.git
url = https://github.com/TelegramMessenger/rlottie.git
[submodule "build-system/bazel-rules/rules_apple"]
path = build-system/bazel-rules/rules_apple
url=https://github.com/ali-fareed/rules_apple.git
Expand All @@ -13,7 +13,7 @@ url=https://github.com/bazelbuild/rules_swift.git
url = https://github.com/bazelbuild/apple_support.git
[submodule "submodules/TgVoipWebrtc/tgcalls"]
path = submodules/TgVoipWebrtc/tgcalls
url=../tgcalls.git
url = https://github.com/TelegramMessenger/tgcalls.git
[submodule "third-party/libvpx/libvpx"]
path = third-party/libvpx/libvpx
url = https://github.com/webmproject/libvpx.git
Expand Down
Loading