The side menu called the app Telegram Desktop and showed only the base version number, so an installed dev build looked exactly like the stable release it was built from. It now names ZaStoGram and carries the pre-release number CI built it as.
887 lines
38 KiB
YAML
887 lines
38 KiB
YAML
name: Windows.
|
|
|
|
on:
|
|
push:
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- '**.md'
|
|
- '!docs/building-win*.md'
|
|
- 'changelog.txt'
|
|
- 'LEGAL'
|
|
- 'LICENSE'
|
|
- '.github/**'
|
|
- '!.github/workflows/win.yml'
|
|
- 'lib/xdg/**'
|
|
- 'snap/**'
|
|
- 'Telegram/build/docker/**'
|
|
- 'Telegram/Resources/uwp/**'
|
|
- 'Telegram/SourceFiles/platform/linux/**'
|
|
- 'Telegram/SourceFiles/platform/mac/**'
|
|
- 'Telegram/Telegram/**'
|
|
- 'Telegram/configure.sh'
|
|
- 'Telegram/Telegram.plist'
|
|
pull_request:
|
|
paths-ignore:
|
|
- 'docs/**'
|
|
- '**.md'
|
|
- '!docs/building-win*.md'
|
|
- 'changelog.txt'
|
|
- 'LEGAL'
|
|
- 'LICENSE'
|
|
- '.github/**'
|
|
- '!.github/workflows/win.yml'
|
|
- 'lib/xdg/**'
|
|
- 'snap/**'
|
|
- 'Telegram/build/docker/**'
|
|
- 'Telegram/Resources/uwp/**'
|
|
- 'Telegram/SourceFiles/platform/linux/**'
|
|
- 'Telegram/SourceFiles/platform/mac/**'
|
|
- 'Telegram/Telegram/**'
|
|
- 'Telegram/configure.sh'
|
|
- 'Telegram/Telegram.plist'
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
|
|
windows:
|
|
name: Windows
|
|
runs-on: ${{ matrix.arch == 'arm64' && 'windows-11-arm' || ((github.event_name == 'pull_request' || github.ref == 'refs/heads/nightly') && 'depot-windows-latest-16' || 'windows-latest') }}
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: write # prune superseded cache entries
|
|
|
|
strategy:
|
|
matrix:
|
|
# x64 builds with Qt 6 to get the QRhi (D3D11) GPU rendering path;
|
|
# the 32-bit build stays on Qt 5 for older Windows versions.
|
|
include:
|
|
- arch: x64_x86
|
|
qt: ""
|
|
generator: ""
|
|
- arch: x64
|
|
qt: "qt6"
|
|
generator: ""
|
|
|
|
env:
|
|
UPLOAD_ARTIFACT: "true"
|
|
ONLY_CACHE: "false"
|
|
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
|
|
# CACHE_KEY (in "First set up") auto-hashes prepare.py recipes + win.bat + SDK.
|
|
# CACHE_VERSION is a manual kill-switch: bump it to invalidate ALL caches at
|
|
# once for changes that are NOT auto-detected — the disk-trim filter or the
|
|
# cache path lists below, and as a safety net for a toolset bump.
|
|
CACHE_VERSION: "v1"
|
|
# LIBS_CACHE_VERSION is the same kill-switch, narrowed to the Libraries
|
|
# tarball, so a trim-filter change that only invalidates library artifacts
|
|
# does not also throw away the (far more expensive) Qt cache.
|
|
LIBS_CACHE_VERSION: "v2"
|
|
TELEGRAM_BUILD_CACHE_VERSION: "v3"
|
|
SCCACHE_GHA_ENABLED: "true"
|
|
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
|
|
SCCACHE_IDLE_TIMEOUT: "600"
|
|
SCCACHE_LOG: warn
|
|
|
|
defaults:
|
|
run:
|
|
shell: cmd
|
|
|
|
steps:
|
|
- name: Prepare directories.
|
|
run: |
|
|
mkdir %userprofile%\TBuild\Libraries
|
|
mklink /d %GITHUB_WORKSPACE%\TBuild %userprofile%\TBuild
|
|
echo TBUILD=%GITHUB_WORKSPACE%\TBuild>>%GITHUB_ENV%
|
|
echo LibrariesPath=%GITHUB_WORKSPACE%\TBuild\Libraries${{ matrix.arch == 'x64' && '\win64' || '' }}>>%GITHUB_ENV%
|
|
|
|
- name: Get repository name.
|
|
shell: bash
|
|
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
|
|
|
- name: Clone.
|
|
uses: actions/checkout@v7
|
|
with:
|
|
submodules: recursive
|
|
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
|
|
|
- name: First set up.
|
|
shell: bash
|
|
run: |
|
|
DOCPATH=$TBUILD/$REPO_NAME/docs/building-win.md
|
|
SDK="$(grep "SDK version" $DOCPATH | sed -r 's/.*\*\*(.*)\*\* SDK version.*/\1/')"
|
|
echo "SDK=$SDK" >> $GITHUB_ENV
|
|
printf 'SCCACHE_BASEDIRS=%s\\%s\n' "$TBUILD" "$REPO_NAME" >> $GITHUB_ENV
|
|
printf 'SCCACHE_GHA_VERSION=%s\n' "${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-${SDK}-${{ matrix.defines || 'default' }}-msvc-v1" >> $GITHUB_ENV
|
|
echo "SCCACHE_ERROR_LOG=$RUNNER_TEMP/sccache_log.txt" >> $GITHUB_ENV
|
|
case "${{ matrix.arch }}" in
|
|
x64_x86) SCCACHE_PORT_OFFSET=0 ;;
|
|
x64) SCCACHE_PORT_OFFSET=1 ;;
|
|
arm64) SCCACHE_PORT_OFFSET=2 ;;
|
|
*) SCCACHE_PORT_OFFSET=3 ;;
|
|
esac
|
|
SCCACHE_SERVER_PORT=$(( 4200 + (GITHUB_RUN_ID % 1000) * 4 + SCCACHE_PORT_OFFSET ))
|
|
echo "SCCACHE_SERVER_PORT=$SCCACHE_SERVER_PORT" >> $GITHUB_ENV
|
|
|
|
sed -i '/CMAKE_${lang}_FLAGS_DEBUG_INIT/s/${_Zi}//' "$PROGRAMFILES"/CMake/share/cmake*/Modules/Platform/Windows-MSVC.cmake
|
|
|
|
# CACHE_KEY hashes the recipe-relevant content of prepare.py (comment-only
|
|
# and blank lines dropped, so formatting churn does not rotate the key) plus
|
|
# win.bat and the SDK. Per-library cache_keys inside prepare.py re-validate
|
|
# every stage regardless, so CACHE_KEY only affects which tarball we select.
|
|
{ grep -vE '^[[:space:]]*#' "$TBUILD/$REPO_NAME/$PREPARE_PATH" || true; } | sed '/^[[:space:]]*$/d' | sha256sum | awk '{ print $1 }' >> CACHE_KEY.txt
|
|
sha256sum "$TBUILD/$REPO_NAME/$(dirname "$PREPARE_PATH")/win.bat" | awk '{ print $1 }' >> CACHE_KEY.txt
|
|
echo "$SDK" >> CACHE_KEY.txt
|
|
CACHE_KEY_VALUE="$(sha256sum CACHE_KEY.txt | awk '{ print $1 }')"
|
|
echo "CACHE_KEY=$CACHE_KEY_VALUE" >> $GITHUB_ENV
|
|
|
|
TELEGRAM_BUILD_CACHE_SCOPE="${{ runner.OS }}-${{ matrix.arch }}-telegram-build-${TELEGRAM_BUILD_CACHE_VERSION}-${SDK}-${{ matrix.qt || 'qt' }}-${{ matrix.generator || 'ninja-multi-config' }}-${{ matrix.defines || 'default' }}-${{ (github.ref == 'refs/heads/nightly' || startsWith(github.ref, 'refs/tags/')) && 'lto' || 'nolto' }}-${CACHE_KEY_VALUE}"
|
|
{
|
|
printf '%s\n' "$TELEGRAM_BUILD_CACHE_SCOPE"
|
|
git -C "$TBUILD/$REPO_NAME" rev-parse HEAD
|
|
git -C "$TBUILD/$REPO_NAME" submodule status --recursive
|
|
sha256sum "$TBUILD/$REPO_NAME/.github/workflows/win.yml"
|
|
sha256sum "$TBUILD/$REPO_NAME/CMakeLists.txt"
|
|
sha256sum "$TBUILD/$REPO_NAME/Telegram/CMakeLists.txt"
|
|
sha256sum "$TBUILD/$REPO_NAME/Telegram/build/prepare/win.bat"
|
|
} > TELEGRAM_BUILD_CACHE_KEY.txt
|
|
TELEGRAM_BUILD_CACHE_KEY="$(sha256sum TELEGRAM_BUILD_CACHE_KEY.txt | awk '{ print $1 }')"
|
|
TELEGRAM_BUILD_CACHE_RESTORE_KEY="$TELEGRAM_BUILD_CACHE_SCOPE-$TELEGRAM_BUILD_CACHE_KEY"
|
|
TELEGRAM_BUILD_CACHE_LOOKUP_KEY="${TELEGRAM_BUILD_CACHE_RESTORE_KEY}-lookup-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
TELEGRAM_BUILD_CACHE_PROGRESS_KEY="${TELEGRAM_BUILD_CACHE_RESTORE_KEY}-progress-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
echo "TELEGRAM_BUILD_CACHE_SCOPE=$TELEGRAM_BUILD_CACHE_SCOPE" >> $GITHUB_ENV
|
|
echo "TELEGRAM_BUILD_CACHE_KEY=$TELEGRAM_BUILD_CACHE_KEY" >> $GITHUB_ENV
|
|
echo "TELEGRAM_BUILD_CACHE_RESTORE_KEY=$TELEGRAM_BUILD_CACHE_RESTORE_KEY" >> $GITHUB_ENV
|
|
echo "TELEGRAM_BUILD_CACHE_LOOKUP_KEY=$TELEGRAM_BUILD_CACHE_LOOKUP_KEY" >> $GITHUB_ENV
|
|
echo "TELEGRAM_BUILD_CACHE_PROGRESS_KEY=$TELEGRAM_BUILD_CACHE_PROGRESS_KEY" >> $GITHUB_ENV
|
|
|
|
echo "Configurate git for cherry-picks."
|
|
git config --global user.email "you@example.com"
|
|
git config --global user.name "Sample"
|
|
|
|
- name: Proxy control-plane source guards.
|
|
shell: bash
|
|
run: |
|
|
cd "$TBUILD/$REPO_NAME"
|
|
python Telegram/SourceFiles/tests/test_mtproto_relay_silence.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_client_hello_profiles.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_endpoint_identity.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_faketls_hardening.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_minimal_hotfix.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_phase_cooldown.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_policy.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_target_flow.py
|
|
python Telegram/SourceFiles/tests/test_mtproxy_tls_psk.py
|
|
python Telegram/SourceFiles/tests/test_proxy_atomic_switch.py
|
|
python Telegram/SourceFiles/tests/test_proxy_capability_cache.py
|
|
python Telegram/SourceFiles/tests/test_proxy_capability_key_contract.py
|
|
python Telegram/SourceFiles/tests/test_proxy_connection_status.py
|
|
python Telegram/SourceFiles/tests/test_proxy_control_plane.py
|
|
python Telegram/SourceFiles/tests/test_proxy_control_plane_truth_table.py
|
|
python Telegram/SourceFiles/tests/test_proxy_diagnostics.py
|
|
python Telegram/SourceFiles/tests/test_proxy_list_checks.py
|
|
python Telegram/SourceFiles/tests/test_proxy_logging_events.py
|
|
python Telegram/SourceFiles/tests/test_proxy_shield_probe.py
|
|
python Telegram/SourceFiles/tests/test_proxy_wss_default.py
|
|
python Telegram/SourceFiles/tests/test_session_endpoint_cooldown.py
|
|
|
|
- name: Install VS 2026 Build Tools.
|
|
if: matrix.arch == 'arm64'
|
|
shell: pwsh
|
|
run: |
|
|
Invoke-WebRequest -Uri https://aka.ms/vs/18/stable/vs_buildtools.exe -OutFile vs_buildtools.exe
|
|
$p = Start-Process -FilePath .\vs_buildtools.exe -Wait -PassThru -ArgumentList `
|
|
'--quiet','--wait','--norestart','--nocache', `
|
|
'--add','Microsoft.VisualStudio.Workload.VCTools', `
|
|
'--add','Microsoft.VisualStudio.Component.VC.Tools.ARM64', `
|
|
'--add','Microsoft.VisualStudio.Component.VC.ATL.ARM64'
|
|
Remove-Item vs_buildtools.exe -Force
|
|
if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) { exit $p.ExitCode }
|
|
|
|
- uses: Eden-CI/msvc-dev-cmd@master
|
|
name: Native Tools Command Prompt.
|
|
with:
|
|
arch: ${{ matrix.arch }}
|
|
sdk: ${{ env.SDK }}
|
|
toolset: ${{ case(matrix.arch == 'arm64', '', '14.44') }}
|
|
|
|
- name: NuGet sources.
|
|
run: |
|
|
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
|
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
|
|
|
- name: ThirdParty cache (restore).
|
|
id: cache-third-party
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: ${{ env.TBUILD }}\ThirdParty
|
|
key: ${{ runner.OS }}-${{ runner.arch }}-third-party-${{ env.CACHE_VERSION }}-${{ env.SDK }}-${{ env.CACHE_KEY }}
|
|
restore-keys: |
|
|
${{ runner.OS }}-${{ runner.arch }}-third-party-${{ env.CACHE_VERSION }}-${{ env.SDK }}-
|
|
${{ runner.OS }}-${{ runner.arch }}-third-party-${{ env.CACHE_VERSION }}-
|
|
${{ runner.OS }}-${{ runner.arch }}-third-party-
|
|
|
|
- name: Libraries cache (restore).
|
|
id: cache-libs
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: |
|
|
${{ env.LibrariesPath }}\*
|
|
!${{ env.LibrariesPath }}\cache_keys
|
|
!${{ env.LibrariesPath }}\[qQ]t[_-]*
|
|
${{ env.LibrariesPath }}\cache_keys\*
|
|
!${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]*
|
|
key: ${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.LIBS_CACHE_VERSION }}-${{ env.SDK }}-${{ env.CACHE_KEY }}
|
|
restore-keys: |
|
|
${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.LIBS_CACHE_VERSION }}-${{ env.SDK }}-
|
|
${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.LIBS_CACHE_VERSION }}-
|
|
|
|
- name: Qt cache (restore).
|
|
id: cache-qt
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: |
|
|
${{ env.LibrariesPath }}\[qQ]t[_-]*
|
|
${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]*
|
|
key: ${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-${{ env.CACHE_VERSION }}-${{ env.SDK }}-${{ env.CACHE_KEY }}
|
|
restore-keys: |
|
|
${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-${{ env.CACHE_VERSION }}-${{ env.SDK }}-
|
|
${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-${{ env.CACHE_VERSION }}-
|
|
${{ runner.OS }}-${{ matrix.arch }}-${{ matrix.qt || 'qt' }}-
|
|
|
|
# Self-healing sanity check on the restored caches, before win.bat trusts them.
|
|
# prepare.py marks a stage 'Good' if <dir>/<name> exists and cache_keys/<name>
|
|
# matches — it never looks INSIDE the dir, so a truncated restore (zero-byte
|
|
# .lib) or a dir emptied by the trim filter would be silently reused. Deleting
|
|
# a per-stage key only forces THAT stage to rebuild (always safe), so we err
|
|
# toward deleting on any corruption signal. Never fails the job.
|
|
- name: Verify restored caches.
|
|
shell: bash
|
|
run: |
|
|
shopt -s nullglob
|
|
check_tree() {
|
|
local root="$1"
|
|
[ -d "$root/cache_keys" ] || return 0
|
|
local key name dir
|
|
for key in "$root"/cache_keys/*; do
|
|
name="$(basename "$key")"
|
|
dir="$root/$name"
|
|
if [ ! -d "$dir" ]; then
|
|
rm -f "$key"; echo "smoke: $name -> orphan key (no artifact dir), will rebuild"; continue
|
|
fi
|
|
if [ -n "$(find "$dir" -type f \( -name '*.lib' -o -name '*.a' -o -name '*.exe' \) -size 0 -print -quit)" ]; then
|
|
rm -f "$key"; echo "smoke: $name -> zero-byte artifact (truncated restore), will rebuild"; continue
|
|
fi
|
|
done
|
|
}
|
|
# The trim filter keeps build products only, so a stage that a LATER
|
|
# stage consumes as sources comes back from the cache stripped of the
|
|
# files that stage needs: ffmpeg's build_ffmpeg_win.sh runs `make
|
|
# install` inside nv-codec-headers, and its Makefile is not a build
|
|
# product. The dir survives (its headers are kept), so the generic
|
|
# checks above see nothing wrong. Drop the key when the probe file is
|
|
# missing — prepare.py wipes and re-clones the stage, which is cheap.
|
|
check_sources() {
|
|
local root="$1" name="$2" probe="$3"
|
|
[ -d "$root/$name" ] || return 0
|
|
[ -f "$root/$name/$probe" ] && return 0
|
|
rm -f "$root/cache_keys/$name"
|
|
echo "smoke: $name -> sources trimmed ($probe missing), will rebuild"
|
|
}
|
|
check_tree "${LibrariesPath//\\//}"
|
|
check_tree "${TBUILD//\\//}/ThirdParty"
|
|
check_sources "${LibrariesPath//\\//}" nv-codec-headers Makefile
|
|
echo "smoke: cache verification done"
|
|
|
|
- name: Libraries.
|
|
id: build-libs
|
|
run: |
|
|
%TBUILD%\%REPO_NAME%\Telegram\build\prepare\win.bat skip-debug silent ${{ matrix.qt }}
|
|
|
|
- name: sccache.
|
|
if: env.ONLY_CACHE == 'false'
|
|
uses: mozilla-actions/sccache-action@v0.0.10
|
|
with:
|
|
version: "v0.16.0"
|
|
disable_annotations: "true"
|
|
|
|
- name: Start sccache server.
|
|
if: env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
run: |
|
|
${SCCACHE_PATH:-sccache} --start-server
|
|
${SCCACHE_PATH:-sccache} --zero-stats
|
|
|
|
- name: Read configuration matrix.
|
|
shell: bash
|
|
run: |
|
|
ARTIFACT_NAME="Telegram"
|
|
|
|
ARCH=""
|
|
if [ -n "${{ matrix.arch }}" ]; then
|
|
case "${{ matrix.arch }}" in
|
|
x64_x86) ARCH="x86";;
|
|
arm64) ARCH="arm";;
|
|
*) ARCH="${{ matrix.arch }}";;
|
|
esac
|
|
echo "Architecture from matrix: $ARCH"
|
|
ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.arch }}"
|
|
fi
|
|
|
|
if [ -n "${{ matrix.qt }}" ]; then
|
|
ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.qt }}"
|
|
fi
|
|
|
|
GENERATOR="-G \"Ninja Multi-Config\""
|
|
echo "Generator: $GENERATOR"
|
|
if [ -n "${{ matrix.generator }}" ]; then
|
|
GENERATOR="-G \"${{ matrix.generator }}\""
|
|
echo "Generator from matrix: $GENERATOR"
|
|
ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.generator }}"
|
|
fi
|
|
echo "TDESKTOP_BUILD_GENERATOR=$GENERATOR" >> $GITHUB_ENV
|
|
|
|
[ -n "$GENERATOR" ] && ARCH=""
|
|
echo "TDESKTOP_BUILD_ARCH=$ARCH" >> $GITHUB_ENV
|
|
|
|
DEFINE=""
|
|
if [ -n "${{ matrix.defines }}" ]; then
|
|
DEFINE="-D ${{ matrix.defines }}=ON"
|
|
echo "Define from matrix: $DEFINE"
|
|
ARTIFACT_NAME="${ARTIFACT_NAME} ${{ matrix.defines }}"
|
|
fi
|
|
echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV
|
|
|
|
echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV
|
|
|
|
API="-D TDESKTOP_API_TEST=ON"
|
|
if [ $GITHUB_REF == 'refs/heads/nightly' ]; then
|
|
echo "Use the open credentials."
|
|
API="-D TDESKTOP_API_ID=611335 -D TDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c"
|
|
fi
|
|
echo "TDESKTOP_BUILD_API=$API" >> $GITHUB_ENV
|
|
|
|
# Keeps build products only — plus the few inputs later stages read back out
|
|
# of the tree: *.pc (ffmpeg's configure resolves dav1d/opus/... through
|
|
# pkg-config) and Makefile (build_ffmpeg_win.sh runs `make install` inside
|
|
# nv-codec-headers). Dropping those makes any ffmpeg rebuild fail against an
|
|
# otherwise valid cache. Bump LIBS_CACHE_VERSION when this filter changes.
|
|
- name: Free up some disk space.
|
|
shell: bash
|
|
run: find $LibrariesPath '(' '(' ! '(' -name '*.lib' -o -name '*.a' -o -name '*.exe' -o -name '*.h' -o -name '*.hpp' -o -name '*.inc' -o -name '*.cmake' -o -name '*.pc' -o -name 'Makefile' -o -path '*/include/*' -o -path '*/objects-*' -o -path '*/cache_keys/*' -o -path '*/patches/*' ')' -type f ')' -o -empty ')' -delete
|
|
|
|
# Save caches BEFORE the (possibly failing) Telegram compile, gated on the
|
|
# libraries step having succeeded — not on the whole job. This is the fix for
|
|
# "compile fails -> nothing cached -> libraries rebuilt every run".
|
|
# !cancelled() -> save on compile failure, skip on cancellation
|
|
# build-libs.outcome == 'success' -> never cache a half-built libraries tree
|
|
# cache-hit != 'true' -> don't re-save an already-present exact key
|
|
- name: ThirdParty cache (save).
|
|
if: >-
|
|
!cancelled()
|
|
&& steps.build-libs.outcome == 'success'
|
|
&& steps.cache-third-party.outputs.cache-hit != 'true'
|
|
continue-on-error: true
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: ${{ env.TBUILD }}\ThirdParty
|
|
key: ${{ steps.cache-third-party.outputs.cache-primary-key }}
|
|
|
|
- name: Libraries cache (save).
|
|
if: >-
|
|
!cancelled()
|
|
&& steps.build-libs.outcome == 'success'
|
|
&& steps.cache-libs.outputs.cache-hit != 'true'
|
|
continue-on-error: true
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: |
|
|
${{ env.LibrariesPath }}\*
|
|
!${{ env.LibrariesPath }}\cache_keys
|
|
!${{ env.LibrariesPath }}\[qQ]t[_-]*
|
|
${{ env.LibrariesPath }}\cache_keys\*
|
|
!${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]*
|
|
key: ${{ steps.cache-libs.outputs.cache-primary-key }}
|
|
|
|
- name: Qt cache (save).
|
|
if: >-
|
|
!cancelled()
|
|
&& steps.build-libs.outcome == 'success'
|
|
&& steps.cache-qt.outputs.cache-hit != 'true'
|
|
continue-on-error: true
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: |
|
|
${{ env.LibrariesPath }}\[qQ]t[_-]*
|
|
${{ env.LibrariesPath }}\cache_keys\[qQ]t[_-]*
|
|
key: ${{ steps.cache-qt.outputs.cache-primary-key }}
|
|
|
|
- name: Telegram build cache (restore).
|
|
if: env.ONLY_CACHE == 'false'
|
|
id: cache-telegram-build
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: |
|
|
${{ env.TBUILD }}\${{ env.REPO_NAME }}\out
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Release\Telegram.exe
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Release\Telegram.pdb
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Telegram\Release\Telegram.lib
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Telegram\Release\Telegram.exp
|
|
key: ${{ env.TELEGRAM_BUILD_CACHE_LOOKUP_KEY }}
|
|
restore-keys: |
|
|
${{ env.TELEGRAM_BUILD_CACHE_RESTORE_KEY }}-progress-
|
|
${{ env.TELEGRAM_BUILD_CACHE_RESTORE_KEY }}
|
|
${{ env.TELEGRAM_BUILD_CACHE_SCOPE }}-
|
|
|
|
- name: Normalize Telegram source mtimes.
|
|
if: env.ONLY_CACHE == 'false' && steps.cache-telegram-build.outputs.cache-matched-key != ''
|
|
shell: bash
|
|
env:
|
|
TELEGRAM_BUILD_CACHE_HIT: ${{ steps.cache-telegram-build.outputs.cache-hit }}
|
|
TELEGRAM_BUILD_CACHE_MATCHED_KEY: ${{ steps.cache-telegram-build.outputs.cache-matched-key }}
|
|
run: |
|
|
cd "${TBUILD//\\//}/${REPO_NAME}"
|
|
python - <<'PY'
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
repo = Path.cwd()
|
|
metadata = repo / "out" / ".telegram_build_cache_metadata"
|
|
old_mtime = 946684800
|
|
|
|
def run_git(args, cwd=repo, check=True):
|
|
result = subprocess.run(
|
|
["git", *args],
|
|
cwd=cwd,
|
|
check=check,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL)
|
|
return result.stdout
|
|
|
|
def valid_commit(value):
|
|
if not value or set(value) == {"0"}:
|
|
return False
|
|
return subprocess.run(
|
|
["git", "cat-file", "-e", f"{value}^{{commit}}"],
|
|
cwd=repo,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL).returncode == 0
|
|
|
|
def ensure_commit(value):
|
|
if not value or set(value) == {"0"}:
|
|
return False
|
|
if valid_commit(value):
|
|
return True
|
|
subprocess.run(
|
|
["git", "fetch", "--no-tags", "--depth=1", "origin", value],
|
|
cwd=repo,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL)
|
|
return valid_commit(value)
|
|
|
|
cached_head = ""
|
|
if metadata.exists():
|
|
for line in metadata.read_text(encoding="utf-8").splitlines():
|
|
if line.startswith("head="):
|
|
cached_head = line.split("=", 1)[1].strip()
|
|
break
|
|
|
|
if (
|
|
not valid_commit(cached_head)
|
|
and os.environ.get("TELEGRAM_BUILD_CACHE_HIT") == "true"
|
|
):
|
|
cached_head = run_git(["rev-parse", "HEAD"]).decode().strip()
|
|
|
|
legacy_cache_heads = {
|
|
"97e7512e600590ca2254c7bd523c2b07346b494f8c07d2334e9fff922a2c7e73":
|
|
"dc7fc515605489f2486904c1a2d3e60811335b54",
|
|
"e48a776cea3c96cdf4cfbf0bcd71426b2945ba854f733ac6c1774413f89253bb":
|
|
"dc7fc515605489f2486904c1a2d3e60811335b54",
|
|
}
|
|
matched_key = os.environ.get("TELEGRAM_BUILD_CACHE_MATCHED_KEY", "")
|
|
if not ensure_commit(cached_head):
|
|
for suffix, head in legacy_cache_heads.items():
|
|
if matched_key.endswith(suffix):
|
|
cached_head = head
|
|
break
|
|
|
|
if not ensure_commit(cached_head):
|
|
print("mtime: no comparable cached head, leaving checkout mtimes")
|
|
raise SystemExit(0)
|
|
|
|
changed = set(filter(None, run_git([
|
|
"diff",
|
|
"--name-only",
|
|
"-z",
|
|
cached_head,
|
|
"HEAD",
|
|
"--",
|
|
]).decode("utf-8", "surrogateescape").split("\0")))
|
|
|
|
cmake_graph_inputs = (
|
|
"CMakeLists.txt",
|
|
"Telegram/CMakeLists.txt",
|
|
"Telegram/cmake/",
|
|
"Telegram/configure.",
|
|
"cmake/",
|
|
)
|
|
cmake_graph_changed = any(
|
|
item.endswith("CMakeLists.txt")
|
|
or item.endswith(".cmake")
|
|
or item.startswith(cmake_graph_inputs)
|
|
for item in changed)
|
|
|
|
def changed_path(path):
|
|
path = path.as_posix()
|
|
return path in changed or any(
|
|
item.startswith(path + "/") for item in changed)
|
|
|
|
def normalize_file(path):
|
|
try:
|
|
os.utime(path, (old_mtime, old_mtime))
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
for name in filter(None, run_git(["ls-files", "-z"]).split(b"\0")):
|
|
rel = Path(os.fsdecode(name))
|
|
if not changed_path(rel):
|
|
normalize_file(repo / rel)
|
|
|
|
submodule_paths = []
|
|
config = run_git(
|
|
["config", "--file", ".gitmodules", "--get-regexp", "path"],
|
|
check=False).decode("utf-8", "replace")
|
|
for line in config.splitlines():
|
|
parts = line.split(maxsplit=1)
|
|
if len(parts) == 2:
|
|
submodule_paths.append(Path(parts[1]))
|
|
|
|
for rel_root in submodule_paths:
|
|
if changed_path(rel_root):
|
|
continue
|
|
root = repo / rel_root
|
|
if not root.exists():
|
|
continue
|
|
files = run_git(["ls-files", "-z"], cwd=root, check=False)
|
|
for name in filter(None, files.split(b"\0")):
|
|
normalize_file(root / Path(os.fsdecode(name)))
|
|
|
|
print(
|
|
f"mtime: normalized unchanged sources from {cached_head[:12]} "
|
|
f"to keep restored Ninja outputs reusable")
|
|
cache = repo / "out" / "CMakeCache.txt"
|
|
if cache.exists() and not cmake_graph_changed:
|
|
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as env:
|
|
env.write("TELEGRAM_SKIP_CONFIGURE=true\n")
|
|
print("mtime: restored CMake configure can be reused")
|
|
elif cmake_graph_changed:
|
|
print("mtime: CMake graph changed, configure will run")
|
|
PY
|
|
|
|
- name: Bake ZaStoGram build id.
|
|
if: env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
run: |
|
|
# Must run after the mtime normalization: that step rewinds the mtime
|
|
# of every unchanged tracked file, so baking earlier would let Ninja
|
|
# keep the object compiled with the previous build id.
|
|
cd "${TBUILD//\\//}/${REPO_NAME}"
|
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
# A tagged release is fully identified by its version number.
|
|
BUILD_ID=""
|
|
else
|
|
BUILD_ID="dev-${GITHUB_RUN_NUMBER}"
|
|
fi
|
|
HEADER=Telegram/SourceFiles/core/zsg_build.h
|
|
sed -i "s|^constexpr auto ZsgBuildId = .*|constexpr auto ZsgBuildId = \"$BUILD_ID\"_cs;|" "$HEADER"
|
|
if ! grep -qF "constexpr auto ZsgBuildId = \"$BUILD_ID\"_cs;" "$HEADER"; then
|
|
echo "Failed to bake the build id into $HEADER!"
|
|
exit 1
|
|
fi
|
|
tail -n 1 "$HEADER"
|
|
|
|
- name: Prepare update signing keys.
|
|
if: env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
env:
|
|
PACKER_PRIVATE_H: ${{ secrets.PACKER_PRIVATE_H }}
|
|
run: |
|
|
mkdir -p "$TBUILD/DesktopPrivate"
|
|
if [ -n "$PACKER_PRIVATE_H" ]; then
|
|
printf '%s' "$PACKER_PRIVATE_H" > "$TBUILD/DesktopPrivate/packer_private.h"
|
|
echo "Using real update signing key from secrets."
|
|
else
|
|
{
|
|
echo '// Placeholder: real keys come from the PACKER_PRIVATE_H secret.'
|
|
echo 'const char *PrivateKey = "";'
|
|
echo 'const char *PrivateBetaKey = "";'
|
|
} > "$TBUILD/DesktopPrivate/packer_private.h"
|
|
echo "PACKER_PRIVATE_H secret is not set, Packer will not be able to sign."
|
|
fi
|
|
echo 'static const char *AlphaPrivateKey = "";' > "$TBUILD/DesktopPrivate/alpha_private.h"
|
|
|
|
- name: Telegram Desktop build.
|
|
id: build-telegram
|
|
if: env.ONLY_CACHE == 'false'
|
|
run: |
|
|
cd %TBUILD%\%REPO_NAME%\Telegram
|
|
|
|
if "%TELEGRAM_SKIP_CONFIGURE%"=="true" (
|
|
echo Reusing restored CMake configure.
|
|
) else (
|
|
call configure.bat ^
|
|
%TDESKTOP_BUILD_GENERATOR% ^
|
|
%TDESKTOP_BUILD_ARCH% ^
|
|
${{ matrix.qt }} ^
|
|
%TDESKTOP_BUILD_API% ^
|
|
-D CMAKE_CONFIGURATION_TYPES=Release ^
|
|
-D DESKTOP_APP_ENABLE_LTO=${{ (github.ref == 'refs/heads/nightly' || startsWith(github.ref, 'refs/tags/')) && 'ON' || 'OFF' }} ^
|
|
-D CMAKE_C_COMPILER_LAUNCHER=sccache ^
|
|
-D CMAKE_CXX_COMPILER_LAUNCHER=sccache ^
|
|
-D CMAKE_COMPILE_WARNING_AS_ERROR=ON ^
|
|
-D CMAKE_MSVC_DEBUG_INFORMATION_FORMAT= ^
|
|
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
|
-D ZASTOGRAM_PACKER=ON ^
|
|
-D DESKTOP_APP_DISABLE_QT_PLUGINS=ON ^
|
|
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
|
%TDESKTOP_BUILD_DEFINE%
|
|
)
|
|
|
|
cmake --build ..\out --config Release --target Telegram Packer --parallel
|
|
|
|
- name: Telegram build cache metadata.
|
|
if: >-
|
|
always()
|
|
&& !cancelled()
|
|
&& env.ONLY_CACHE == 'false'
|
|
&& steps.build-telegram.outcome != 'skipped'
|
|
shell: bash
|
|
run: |
|
|
cd "${TBUILD//\\//}/${REPO_NAME}"
|
|
mkdir -p out
|
|
# A stopped build leaves .obj files that still match the RESTORED
|
|
# cache's head for every source it never got to recompile. Advancing
|
|
# head= past them would make the next run's mtime normalization mark
|
|
# those stale objects as up to date forever (unresolved externals at
|
|
# link time), so on failure keep the restored metadata as is.
|
|
if [ "${{ steps.build-telegram.outcome }}" != "success" ] \
|
|
&& [ -f out/.telegram_build_cache_metadata ]; then
|
|
echo "Build failed, keeping restored cache metadata."
|
|
exit 0
|
|
fi
|
|
{
|
|
printf 'head=%s\n' "$(git rev-parse HEAD)"
|
|
printf 'submodules\n'
|
|
git submodule status --recursive || true
|
|
} > out/.telegram_build_cache_metadata
|
|
|
|
# GitHub cache entries are immutable. Successful builds save the stable
|
|
# primary key; failed builds save a unique progress key, restored first
|
|
# on the next run so late compile failures keep the completed .obj files.
|
|
- name: Telegram build cache (save).
|
|
id: save-telegram-build
|
|
if: >-
|
|
always()
|
|
&& !cancelled()
|
|
&& env.ONLY_CACHE == 'false'
|
|
&& steps.build-telegram.outcome != 'skipped'
|
|
&& (
|
|
steps.build-telegram.outcome == 'failure'
|
|
|| steps.cache-telegram-build.outputs.cache-matched-key != env.TELEGRAM_BUILD_CACHE_RESTORE_KEY
|
|
)
|
|
continue-on-error: true
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: |
|
|
${{ env.TBUILD }}\${{ env.REPO_NAME }}\out
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Release\Telegram.exe
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Release\Telegram.pdb
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Telegram\Release\Telegram.lib
|
|
!${{ env.TBUILD }}\${{ env.REPO_NAME }}\out\Telegram\Release\Telegram.exp
|
|
key: ${{ steps.build-telegram.outcome == 'failure' && env.TELEGRAM_BUILD_CACHE_PROGRESS_KEY || env.TELEGRAM_BUILD_CACHE_RESTORE_KEY }}
|
|
|
|
# Cache entries are immutable and the repo budget is 10 GB. Every push
|
|
# saves a fresh ~1.1-1.3 GB out/ snapshot per arch, so superseded
|
|
# generations would push the repo over budget within a day and LRU
|
|
# eviction would silently drop sccache objects and even the freshest
|
|
# build caches (observed: a run finding NO telegram-build cache at all).
|
|
# After a successful save keep, per arch, only:
|
|
# - the entry just saved (or the exact-key entry we restored from),
|
|
# - entries of the sibling lto/nolto scope with the SAME recipe hash
|
|
# (those are pruned by their own lto/nolto runs).
|
|
# Older generations, rotated scopes and -progress- leftovers are deleted
|
|
# here instead of waiting for the age-based nightly cleanup. Runs only on
|
|
# build success: after a failure the restored-from entry must survive.
|
|
- name: Prune superseded build caches.
|
|
if: >-
|
|
always()
|
|
&& !cancelled()
|
|
&& env.ONLY_CACHE == 'false'
|
|
&& steps.build-telegram.outcome == 'success'
|
|
&& (
|
|
steps.save-telegram-build.outcome == 'success'
|
|
|| steps.cache-telegram-build.outputs.cache-matched-key == env.TELEGRAM_BUILD_CACHE_RESTORE_KEY
|
|
)
|
|
continue-on-error: true
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
PREFIX="${{ runner.OS }}-${{ matrix.arch }}-telegram-build-"
|
|
case "$TELEGRAM_BUILD_CACHE_SCOPE" in
|
|
*-nolto-*) SIBLING="${TELEGRAM_BUILD_CACHE_SCOPE/-nolto-/-lto-}" ;;
|
|
*) SIBLING="${TELEGRAM_BUILD_CACHE_SCOPE/-lto-/-nolto-}" ;;
|
|
esac
|
|
if ! rows=$(gh cache list -R "$GITHUB_REPOSITORY" --key "$PREFIX" \
|
|
--limit 200 --json id,key --jq '.[] | "\(.id)\t\(.key)"'); then
|
|
echo "warn: gh cache list failed, skipping prune" >&2
|
|
exit 0
|
|
fi
|
|
deleted=0
|
|
while IFS=$'\t' read -r id key; do
|
|
[ -n "$id" ] || continue
|
|
[ "$key" = "$TELEGRAM_BUILD_CACHE_RESTORE_KEY" ] && continue
|
|
case "$key" in
|
|
"$SIBLING"*) continue ;;
|
|
esac
|
|
if gh cache delete -R "$GITHUB_REPOSITORY" "$id"; then
|
|
echo "pruned: $key"
|
|
deleted=$((deleted+1))
|
|
else
|
|
echo "warn: failed to delete cache $id ($key)" >&2
|
|
fi
|
|
done <<< "$rows"
|
|
echo "Pruned $deleted superseded cache entrie(s)."
|
|
|
|
- name: sccache stats.
|
|
if: always() && env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
run: |
|
|
if ! command -v "${SCCACHE_PATH:-sccache}" >/dev/null 2>&1; then
|
|
echo "sccache not found, skipping stats."
|
|
exit 0
|
|
fi
|
|
${SCCACHE_PATH:-sccache} --show-stats || true
|
|
|
|
- name: sccache error log.
|
|
if: always() && env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
run: cat "${SCCACHE_ERROR_LOG//\\//}" 2>/dev/null || echo "(no sccache error log)"
|
|
|
|
- name: Stop sccache server.
|
|
if: always() && env.ONLY_CACHE == 'false'
|
|
shell: bash
|
|
run: |
|
|
if ! command -v "${SCCACHE_PATH:-sccache}" >/dev/null 2>&1; then
|
|
echo "sccache not found, skipping stop."
|
|
exit 0
|
|
fi
|
|
${SCCACHE_PATH:-sccache} --stop-server || true
|
|
|
|
- name: Pack signed update.
|
|
if: env.ONLY_CACHE == 'false' && startsWith(github.ref, 'refs/tags/')
|
|
shell: bash
|
|
run: |
|
|
cd "$TBUILD/$REPO_NAME/out/Release"
|
|
VERSION=$(awk '$1 == "AppVersion" { print $2 }' "$TBUILD/$REPO_NAME/Telegram/build/version")
|
|
case "${{ matrix.arch }}" in
|
|
x64) TARGET="-target win64" ;;
|
|
arm64) TARGET="-target winarm" ;;
|
|
*) TARGET="" ;;
|
|
esac
|
|
./Packer.exe -path Telegram.exe -path Updater.exe -version "$VERSION" $TARGET
|
|
ls -la t*upd*
|
|
|
|
- name: Move artifact.
|
|
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
|
run: |
|
|
set OUT=%TBUILD%\%REPO_NAME%\out\Release
|
|
mkdir artifact
|
|
move %OUT%\Telegram.exe artifact/
|
|
move %OUT%\Updater.exe artifact/
|
|
if exist %OUT%\tupdate* move %OUT%\tupdate* artifact/
|
|
if exist %OUT%\tx64upd* move %OUT%\tx64upd* artifact/
|
|
if exist %OUT%\tarm64upd* move %OUT%\tarm64upd* artifact/
|
|
- uses: actions/upload-artifact@v7
|
|
name: Upload artifact.
|
|
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
|
with:
|
|
name: ${{ env.ARTIFACT_NAME }}
|
|
path: artifact\
|
|
|
|
release:
|
|
name: Pre-release
|
|
needs: windows
|
|
if: github.event_name == 'push'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Download artifacts.
|
|
uses: actions/download-artifact@v7
|
|
with:
|
|
path: artifacts
|
|
- name: Collect binaries.
|
|
run: |
|
|
mkdir release
|
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
release_id="$(printf '%s' "$GITHUB_REF_NAME" | sed 's#[/\\:*?"<>|]#-#g')"
|
|
else
|
|
release_id="dev-${GITHUB_RUN_NUMBER}"
|
|
fi
|
|
for d in artifacts/*/; do
|
|
name="$(basename "$d")"
|
|
arch="$(echo "$name" | sed 's/^Telegram //; s/ qt6//; s/ /_/g')"
|
|
if [ -f "$d/Telegram.exe" ]; then
|
|
branded_exe="$GITHUB_WORKSPACE/release/ZaStoGram-$arch.exe"
|
|
cp "$d/Telegram.exe" "$branded_exe"
|
|
zip -j -9 "$GITHUB_WORKSPACE/release/ZaStoGram-$release_id-$arch.zip" "$branded_exe"
|
|
fi
|
|
if [ -f "$d/Telegram.exe" ] && [ -f "$d/Updater.exe" ]; then
|
|
(cd "$d" && zip -j "$GITHUB_WORKSPACE/release/ZaStoGram-portable-$arch.zip" Telegram.exe Updater.exe)
|
|
fi
|
|
cp "$d"/tupdate* "$d"/tx64upd* "$d"/tarm64upd* release/ 2>/dev/null || true
|
|
done
|
|
ls -la release
|
|
- name: Generate update map (current4).
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
run: |
|
|
ver=""
|
|
for f in release/tupdate* release/tx64upd* release/tarm64upd*; do
|
|
[ -e "$f" ] || continue
|
|
ver="$(basename "$f" | grep -oE '[0-9]+$')"
|
|
done
|
|
if [ -z "$ver" ]; then
|
|
echo "No signed update packages found, cannot generate current4!"
|
|
exit 1
|
|
fi
|
|
current='{}'
|
|
add() {
|
|
current="$(echo "$current" | jq --arg k "$1" --arg v "$ver" --arg l "$2" \
|
|
'. + {($k): {stable: {released: $v, link: $l}}}')"
|
|
}
|
|
[ -e "release/tupdate$ver" ] && add win "/tupdate{version}"
|
|
[ -e "release/tx64upd$ver" ] && add win64 "/tx64upd{version}"
|
|
[ -e "release/tarm64upd$ver" ] && add winarm "/tarm64upd{version}"
|
|
echo "$current" | tee release/current4
|
|
- name: Publish release.
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
gh release create "${{ github.ref_name }}" release/* \
|
|
--repo "${{ github.repository }}" \
|
|
--title "ZaStoGram ${{ github.ref_name }}" \
|
|
--generate-notes \
|
|
--latest
|
|
else
|
|
gh release create "dev-${{ github.run_number }}" release/* \
|
|
--repo "${{ github.repository }}" \
|
|
--target "${{ github.sha }}" \
|
|
--title "ZaStoGram dev ${{ github.run_number }}" \
|
|
--notes "Automated pre-release from ${{ github.sha }}." \
|
|
--prerelease
|
|
fi
|