# Сборка BiLauncher под Windows, Linux и macOS и публикация в Gitea Releases. # # push тега vX.Y.Z (или vX.Y.Z-rc1 → pre-release): # create-release создаёт черновик релиза → три сборки прикладывают к нему файлы → # publish добавляет SHA256SUMS.txt и снимает флаг черновика. Если какая-то сборка # упала, релиз остаётся черновиком и пользователям не виден. # Ручной запуск (workflow_dispatch): те же сборки, но результат — артефакты прогона, # релиз не создаётся. # # Пути к Qt на раннерах задаются в их config.yaml (runner.envs): QT_ROOT — kit Qt, # QT_TOOLS — папка Tools из Qt Online Installer (там CMake и Ninja). # Секрет RELEASE_TOKEN — PAT с правом repository: write. name: release on: push: tags: ["v*"] workflow_dispatch: env: API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} jobs: create-release: runs-on: linux outputs: version: ${{ steps.meta.outputs.version }} tag: ${{ steps.meta.outputs.tag }} release_id: ${{ steps.release.outputs.id }} steps: - uses: actions/checkout@v4 - id: meta name: Версия из CMakeLists.txt и сверка с тегом run: | version=$(sed -nE 's/^project\(BiLauncher VERSION ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' CMakeLists.txt) [ -n "$version" ] || { echo "Не нашёл project(BiLauncher VERSION ...)"; exit 1; } tag="" if [ "${GITHUB_REF_TYPE}" = "tag" ]; then tag="${GITHUB_REF_NAME}" base="${tag#v}"; base="${base%%-*}" if [ "$base" != "$version" ]; then echo "Тег $tag не совпадает с версией $version в CMakeLists.txt"; exit 1 fi version="${tag#v}" fi echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "Версия: $version, тег: ${tag:-нет}" - id: release name: Черновик релиза if: steps.meta.outputs.tag != '' env: TOKEN: ${{ secrets.RELEASE_TOKEN }} TAG: ${{ steps.meta.outputs.tag }} run: | # Описание релиза — раздел CHANGELOG.md для этой версии base="${TAG#v}"; base="${base%%-*}" notes=$(awk -v v="$base" ' /^## \[/ { if (on) exit; if (index($0, "[" v "]")) { on=1; next } } on' CHANGELOG.md) prerelease=false; case "$TAG" in *-*) prerelease=true;; esac body=$(python3 -c 'import json,sys; print(json.dumps({"tag_name":sys.argv[1],"name":"BiLauncher "+sys.argv[1],"body":sys.argv[2],"draft":True,"prerelease":sys.argv[3]=="true"}))' \ "$TAG" "$notes" "$prerelease") id=$(curl -fsS -X POST "$API/releases" \ -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ -d "$body" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])') echo "id=$id" >> "$GITHUB_OUTPUT" echo "Создан черновик релиза $TAG (id $id)" build-linux: needs: create-release runs-on: linux env: VERSION: ${{ needs.create-release.outputs.version }} RELEASE_ID: ${{ needs.create-release.outputs.release_id }} outputs: sums: ${{ steps.pack.outputs.sums }} steps: - uses: actions/checkout@v4 - name: Сборка run: | export PATH="$QT_TOOLS/CMake/bin:$QT_TOOLS/Ninja:$PATH" rm -rf build AppDir dist # LIBDIR=lib: zlibreference.cpp ищет libz рядом, в ../lib/BiLauncher, # а на Ubuntu по умолчанию был бы lib/x86_64-linux-gnu. cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="$QT_ROOT" \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib # На ВМ 6 ГБ памяти: без ограничения 42 параллельных компилятора уходят в OOM. cmake --build build -j 8 cmake --install build --prefix "$PWD/AppDir/usr" - id: pack name: AppImage и tar.gz run: | mkdir -p dist cp AppDir/usr/share/applications/bilauncher.desktop AppDir/ cp AppDir/usr/share/icons/hicolor/256x256/apps/bilauncher.png AppDir/ ln -sf usr/bin/BiLauncher AppDir/AppRun ARCH=x86_64 appimagetool --no-appstream AppDir "dist/BiLauncher-$VERSION-x86_64.AppImage" cp -a AppDir/usr "BiLauncher-$VERSION" tar -czf "dist/BiLauncher-$VERSION-linux-x86_64.tar.gz" "BiLauncher-$VERSION" rm -rf "BiLauncher-$VERSION" ls -lh dist echo "sums=$(cd dist && sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" - name: Загрузка в релиз if: env.RELEASE_ID != '' env: TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | for f in dist/*; do curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@$f" "$API/releases/$RELEASE_ID/assets?name=$(basename "$f")" > /dev/null echo "Загружен $(basename "$f")" done - name: Артефакты прогона if: env.RELEASE_ID == '' uses: actions/upload-artifact@v4 with: name: linux path: dist/ build-windows: needs: create-release runs-on: windows env: VERSION: ${{ needs.create-release.outputs.version }} RELEASE_ID: ${{ needs.create-release.outputs.release_id }} outputs: sums: ${{ steps.build.outputs.sums }} steps: - uses: actions/checkout@v4 - id: build name: Сборка, установщик и portable-архив shell: powershell run: | $ErrorActionPreference = "Stop" # Короткие пути: дерево WebEngine легко вылезает за MAX_PATH. $build = "C:\r\bl-build"; $stage = "C:\r\bl-stage"; $dist = "$PWD\dist" Remove-Item -Recurse -Force $build, $stage, $dist -ErrorAction SilentlyContinue $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * ` -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath Import-Module "$vs\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64" $env:PATH = "$env:QT_TOOLS\CMake_64\bin;$env:QT_TOOLS\Ninja;$env:PATH" cmake -S . -B $build -G Ninja -DCMAKE_BUILD_TYPE=Release "-DCMAKE_PREFIX_PATH=$env:QT_ROOT" if ($LASTEXITCODE) { throw "cmake configure" } cmake --build $build if ($LASTEXITCODE) { throw "cmake build" } cmake --install $build --prefix $stage if ($LASTEXITCODE) { throw "cmake install" } New-Item -ItemType Directory -Force $dist | Out-Null & "$env:ISCC_DIR\ISCC.exe" /Q "/DAppVersion=$env:VERSION" "/DSourceDir=$stage" "/DOutputDir=$dist" packaging\windows\BiLauncher.iss if ($LASTEXITCODE) { throw "iscc" } # Portable: то же дерево, что ставит установщик, без vc_redist # (в portable-варианте среда выполнения MSVC должна уже стоять в системе). Remove-Item "$stage\bin\vc_redist.x64.exe" -ErrorAction SilentlyContinue $portable = "C:\r\BiLauncher-$env:VERSION" Remove-Item -Recurse -Force $portable -ErrorAction SilentlyContinue Move-Item $stage $portable # Системный bsdtar по полному пути: в PATH раннера раньше стоит GNU tar из Git, # который не умеет zip и принимает «C:» за имя удалённого хоста. & "$env:SystemRoot\System32\tar.exe" -a -c -f "$dist\BiLauncher-$env:VERSION-win64-portable.zip" -C C:\r "BiLauncher-$env:VERSION" if ($LASTEXITCODE) { throw "zip" } Remove-Item -Recurse -Force $portable Get-ChildItem $dist | Format-Table Name, Length $sums = Get-ChildItem $dist | ForEach-Object { "{0} {1}" -f (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower(), $_.Name } $b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(($sums -join "`n") + "`n")) # Не Out-File: PowerShell 5.1 пишет UTF-8 с BOM, и имя первой переменной ломается. [IO.File]::AppendAllText($env:GITHUB_OUTPUT, "sums=$b64`n") - name: Загрузка в релиз if: env.RELEASE_ID != '' shell: powershell env: TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | foreach ($f in Get-ChildItem dist) { curl.exe -fsS -X POST -H "Authorization: token $env:TOKEN" ` -F "attachment=@$($f.FullName)" "$env:API/releases/$env:RELEASE_ID/assets?name=$($f.Name)" | Out-Null if ($LASTEXITCODE) { throw "Не удалось загрузить $($f.Name)" } "Загружен $($f.Name)" } - name: Артефакты прогона if: env.RELEASE_ID == '' uses: actions/upload-artifact@v4 with: name: windows path: dist/ build-macos: needs: create-release runs-on: macos env: VERSION: ${{ needs.create-release.outputs.version }} RELEASE_ID: ${{ needs.create-release.outputs.release_id }} outputs: sums: ${{ steps.pack.outputs.sums }} steps: - uses: actions/checkout@v4 - name: Сборка (universal, arm64 + x86_64) run: | export PATH="$QT_TOOLS/CMake/CMake.app/Contents/bin:$QT_TOOLS/Ninja:$PATH" rm -rf build stage dist dmgroot cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="$QT_ROOT" \ -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" cmake --build build cmake --install build --prefix "$PWD/stage" - id: pack name: Ad-hoc подпись и DMG run: | # Без Developer ID: ad-hoc подпись нужна, чтобы приложение вообще # запускалось на Apple Silicon; Gatekeeper всё равно спросит подтверждение. codesign --force --deep --sign - stage/BiLauncher.app codesign --verify --deep --strict stage/BiLauncher.app mkdir -p dmgroot dist cp -R stage/BiLauncher.app dmgroot/ ln -s /Applications dmgroot/Applications hdiutil create -volname "BiLauncher" -srcfolder dmgroot -ov -format UDZO \ "dist/BiLauncher-$VERSION-macOS-universal.dmg" rm -rf dmgroot ls -lh dist echo "sums=$(cd dist && shasum -a 256 * | base64 | tr -d '\n')" >> "$GITHUB_OUTPUT" - name: Загрузка в релиз if: env.RELEASE_ID != '' env: TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | for f in dist/*; do curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@$f" "$API/releases/$RELEASE_ID/assets?name=$(basename "$f")" > /dev/null echo "Загружен $(basename "$f")" done - name: Артефакты прогона if: env.RELEASE_ID == '' uses: actions/upload-artifact@v4 with: name: macos path: dist/ publish: needs: [create-release, build-linux, build-windows, build-macos] if: needs.create-release.outputs.release_id != '' runs-on: linux env: RELEASE_ID: ${{ needs.create-release.outputs.release_id }} TOKEN: ${{ secrets.RELEASE_TOKEN }} steps: - name: SHA256SUMS.txt и публикация run: | { echo "${{ needs.build-windows.outputs.sums }}" | base64 -d echo "${{ needs.build-linux.outputs.sums }}" | base64 -d echo "${{ needs.build-macos.outputs.sums }}" | base64 -d } | tr -d '\r' | sed '/^$/d' > SHA256SUMS.txt cat SHA256SUMS.txt curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@SHA256SUMS.txt" "$API/releases/$RELEASE_ID/assets?name=SHA256SUMS.txt" > /dev/null curl -fsS -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ -d '{"draft": false}' "$API/releases/$RELEASE_ID" > /dev/null echo "Релиз опубликован"