0e515604db
build / build (push) Successful in 6s
Раннер сохраняет каждый шаг во временный .ps1 в кодировке, которая портит кириллицу: условие с русским текстом превратилось в мусор и уронило сборку, хотя сама проверка отработала верно. Теперь внутри run: только латиница. Чтобы описание релиза осталось русским, оно вынесено в .gitea/release-body.md и читается как UTF-8. Проверка модели сверяется с машиночитаемой строкой RESULT, а не с человеческой сводкой. Архив назван латиницей по той же причине. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
186 lines
11 KiB
YAML
186 lines
11 KiB
YAML
# Сборка и публикация «Переводчика экрана».
|
||
#
|
||
# Идёт на self-hosted Windows-раннере (метка `windows`, host mode) — иначе никак:
|
||
# распознавание держится на Windows.Media.Ocr, а сборка — на Roslyn из VS Build Tools
|
||
# и на Windows.winmd из Windows SDK. В контейнере ничего этого нет.
|
||
#
|
||
# * push в master -> сборка + прогон проверок + перезапись релиза `latest`
|
||
# * push тега v* -> то же, но отдельный постоянный релиз с этой версией
|
||
#
|
||
# Требований к раннеру всего два, и оба уже выполнены (тот же хост собирает GusVoice):
|
||
# Visual Studio Build Tools (нужен Roslyn csc) и Windows SDK (Windows.winmd).
|
||
# Если Roslyn вдруг отсутствует, build.ps1 сам откатывается на компилятор из состава
|
||
# Windows — код специально написан без возможностей C# 6 и выше, чтобы это работало.
|
||
|
||
name: build
|
||
|
||
on:
|
||
push:
|
||
branches: [master]
|
||
tags: ['v*']
|
||
workflow_dispatch:
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
env:
|
||
GITEA_HOST: gitea.gusik.tech
|
||
|
||
jobs:
|
||
build:
|
||
runs-on: windows
|
||
steps:
|
||
# Обычный actions/checkout тянется с github.com, а до него из этой сети TLS не доходит
|
||
# (`net/http: TLS handshake timeout`). Репозиторий публичный и лежит рядом, поэтому
|
||
# проще и надёжнее склонировать его напрямую, без внешних действий.
|
||
- name: Checkout
|
||
shell: pwsh
|
||
env:
|
||
REPO: ${{ github.repository }}
|
||
SHA: ${{ github.sha }}
|
||
run: |
|
||
$ErrorActionPreference = 'Stop'
|
||
git init -q .
|
||
git remote add origin "https://$($env:GITEA_HOST)/$($env:REPO).git"
|
||
git -c protocol.version=2 fetch -q --depth 1 origin $env:SHA
|
||
git checkout -q FETCH_HEAD
|
||
git log --oneline -1
|
||
|
||
- name: Сборка exe и диагностической утилиты
|
||
shell: pwsh
|
||
run: .\build.ps1 -Test
|
||
|
||
# Модель правдоподобия английского — единственная часть, которую можно проверить
|
||
# без экрана и без языковых пакетов. Проверяет, что таблица биграмм на месте и
|
||
# всё ещё отделяет настоящие надписи от каши распознавания.
|
||
# ВНИМАНИЕ: внутри run: только латиница. Раннер сохраняет шаг во временный .ps1
|
||
# в кодировке, которая портит кириллицу, и условие с русским текстом не сработает
|
||
# (проверено: сравнение превращалось в мусор и валило сборку).
|
||
- name: Проверка биграммной модели
|
||
shell: pwsh
|
||
run: |
|
||
$out = & .\dist\RenderTest.exe --selftest 2>&1 | Out-String
|
||
Write-Host $out
|
||
if ($LASTEXITCODE -ne 0) { throw "selftest exit code $LASTEXITCODE" }
|
||
if ($out -notmatch 'RESULT worst_good=(-?[\d.]+) best_bad=(-?[\d.]+)') { throw "selftest produced no summary" }
|
||
$good = [double]$Matches[1]; $bad = [double]$Matches[2]
|
||
Write-Host "worst good = $good, best bad = $bad"
|
||
# Настоящие надписи не должны опускаться до уровня каши, иначе фильтр начнёт
|
||
# выбрасывать нормальный текст.
|
||
if ($good -lt -3.0) { throw "real phrases scored below the garbage threshold" }
|
||
|
||
# Гейт качества на эталонном кадре. Работает, только если на раннере установлен
|
||
# английский пакет распознавания; если его нет — не валим сборку, а предупреждаем:
|
||
# это среда, а не регрессия кода.
|
||
- name: Проверка распознавания на эталоне
|
||
shell: pwsh
|
||
run: |
|
||
.\tools\make-sample.ps1
|
||
$out = & .\dist\RenderTest.exe --in .\dist\sample.png --out .\dist\ci --no-translate 2>&1 | Out-String
|
||
Write-Host $out
|
||
# Латиница в шаблонах намеренно — см. примечание о кодировке выше.
|
||
if ($out -match 'OCR\s+\S*\s*=?\s*$' -or $out -match 'exit code 2') {
|
||
Write-Host '::warning::no OCR language pack on the runner - frame check skipped'
|
||
exit 0
|
||
}
|
||
if ($out -notmatch '->\s*(\d+)') {
|
||
Write-Host '::warning::no block count in output - frame check skipped'
|
||
exit 0
|
||
}
|
||
$blocks = [int]$Matches[1]
|
||
Write-Host "blocks detected: $blocks"
|
||
# На этом кадре стабильно выходит 27-29 блоков. Резкое расхождение означает, что
|
||
# правки в Blocks.cs поехали — либо всё слиплось, либо рассыпалось.
|
||
if ($blocks -lt 18 -or $blocks -gt 40) { throw "unexpected block count ($blocks) - segmentation broke" }
|
||
|
||
- name: Сборка пакета
|
||
shell: pwsh
|
||
run: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$rel = 'release'
|
||
New-Item -ItemType Directory -Force -Path $rel | Out-Null
|
||
Copy-Item .\dist\ScreenTranslator.exe $rel -Force
|
||
# Инструкция названа по-русски, поэтому берём её по маске, а не по имени:
|
||
# писать кириллицу в этот скрипт нельзя (кодировка раннера).
|
||
Get-ChildItem -Path . -Filter '*.txt' -File | Copy-Item -Destination $rel -Force
|
||
# config.json программа делает сама из вшитого умолчания
|
||
& .\dist\ScreenTranslator.exe --write-config
|
||
Start-Sleep -Seconds 2
|
||
if (Test-Path .\dist\config.json) { Copy-Item .\dist\config.json $rel -Force }
|
||
else { Write-Host '::warning::config.json was not generated' }
|
||
Get-ChildItem $rel | Select-Object Name, Length | Format-Table -AutoSize
|
||
Compress-Archive -Path "$rel\*" -DestinationPath 'ScreenTranslator.zip' -Force
|
||
|
||
# Тег v* -> постоянный релиз. Любой push в master -> перезаписываемый `latest`,
|
||
# чтобы у тех, кому программу отдали, всегда была ссылка на свежую сборку.
|
||
- name: Публикация релиза
|
||
shell: pwsh
|
||
env:
|
||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||
REPO: ${{ github.repository }}
|
||
REF_NAME: ${{ github.ref_name }}
|
||
REF: ${{ github.ref }}
|
||
SHA: ${{ github.sha }}
|
||
run: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$host_ = $env:GITEA_HOST
|
||
$base = "https://${host_}/api/v1/repos/$($env:REPO)"
|
||
$hdr = @{ Authorization = "token $($env:GITEA_TOKEN)" }
|
||
$isTag = $env:REF -like 'refs/tags/*'
|
||
$tag = if ($isTag) { $env:REF_NAME } else { 'latest' }
|
||
|
||
if (-not $isTag) {
|
||
# Скользящий релиз: сносим прошлый вместе с тегом, иначе он останется
|
||
# висеть на старом коммите и «свежая сборка» перестанет быть свежей.
|
||
try {
|
||
$old = Invoke-RestMethod -Uri "$base/releases/tags/$tag" -Headers $hdr
|
||
if ($old.id) {
|
||
Invoke-RestMethod -Method Delete -Uri "$base/releases/$($old.id)" -Headers $hdr | Out-Null
|
||
Write-Host "previous $tag release removed"
|
||
}
|
||
} catch { }
|
||
try { Invoke-RestMethod -Method Delete -Uri "$base/tags/$tag" -Headers $hdr | Out-Null } catch { }
|
||
}
|
||
|
||
$short = $env:SHA.Substring(0, 8)
|
||
$stamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm') + ' UTC'
|
||
# Русский текст описания лежит в файле и читается как UTF-8 — в самом скрипте
|
||
# кириллицу писать нельзя (её ломает кодировка временного .ps1 у раннера).
|
||
$notes = (Get-Content '.gitea/release-body.md' -Raw -Encoding UTF8).
|
||
Replace('{COMMIT}', $short).Replace('{DATE}', $stamp)
|
||
|
||
$body = @{
|
||
tag_name = $tag
|
||
target_commitish = $env:SHA
|
||
name = if ($isTag) { $tag } else { "latest ($short)" }
|
||
body = $notes
|
||
draft = $false
|
||
prerelease = (-not $isTag)
|
||
} | ConvertTo-Json -Compress
|
||
|
||
try {
|
||
$rel = Invoke-RestMethod -Method Post -Uri "$base/releases" -Headers $hdr -ContentType 'application/json' -Body $body
|
||
} catch {
|
||
# Релиз на этот тег уже есть (перезапуск задания) — берём его и обновляем описание
|
||
$rel = Invoke-RestMethod -Uri "$base/releases/tags/$tag" -Headers $hdr
|
||
$patch = @{ body = $notes } | ConvertTo-Json -Compress
|
||
Invoke-RestMethod -Method Patch -Uri "$base/releases/$($rel.id)" -Headers $hdr -ContentType 'application/json' -Body $patch | Out-Null
|
||
}
|
||
if (-not $rel.id) { throw "could not resolve release for tag $tag" }
|
||
|
||
# Старые файлы с тем же именем API не заменяет, поэтому сначала чистим
|
||
try {
|
||
foreach ($a in (Invoke-RestMethod -Uri "$base/releases/$($rel.id)/assets" -Headers $hdr)) {
|
||
Invoke-RestMethod -Method Delete -Uri "$base/releases/$($rel.id)/assets/$($a.id)" -Headers $hdr | Out-Null
|
||
}
|
||
} catch { }
|
||
|
||
foreach ($f in @('ScreenTranslator.zip', '.\dist\ScreenTranslator.exe')) {
|
||
$item = Get-Item $f
|
||
Write-Host "uploading $($item.Name) ..."
|
||
& curl.exe -sf -X POST "$base/releases/$($rel.id)/assets?name=$($item.Name)" `
|
||
-H "Authorization: token $($env:GITEA_TOKEN)" -F "attachment=@$($item.FullName)"
|
||
if ($LASTEXITCODE -ne 0) { throw "upload failed for $($item.Name)" }
|
||
}
|
||
Write-Host "release $tag published: https://${host_}/$($env:REPO)/releases"
|