From 91130aed3366674e88cf31f988f7f0ea1d60986e Mon Sep 17 00:00:00 2001 From: Dmitriy Fofanov Date: Mon, 23 Feb 2026 22:21:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7:=20make=20release=20VERSION=3DX.Y.Z=20GIT=5F?= =?UTF-8?q?TOKEN=3Dxxx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/release.yml | 2 +- Makefile | 223 +++++++++++++++++++++++++++++++---- 2 files changed, 201 insertions(+), 24 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 5e8fc56..a0c32d5 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -28,7 +28,7 @@ name: "Release CI/CD" on: push: tags: - - "v*.*.*" + - "v*" env: GO_VERSION: "1.24" diff --git a/Makefile b/Makefile index 0853d36..91a453c 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,12 @@ ifeq ($(OS),Windows_NT) .SHELLFLAGS = -NoProfile -ExecutionPolicy Bypass -Command BUILD_DATE := $(shell Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ') GIT_COMMIT := $(shell try { git rev-parse --short HEAD 2>$$null } catch { 'unknown' }) + GO_VERSION := $(shell try { (go env GOVERSION) -replace '^go','' } catch { 'unknown' }) else SHELL := /bin/sh BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo unknown) GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) + GO_VERSION := $(shell go env GOVERSION 2>/dev/null | sed 's/^go//' || echo unknown) endif # ============================================================================ @@ -469,32 +471,206 @@ endif # Запускать ТОЛЬКО через команды make ниже — не вручную. # ============================================================================ -## Полный релиз: создать тег → push → CI/CD собирает и публикует -## Использование: make release VERSION=2.1.0 +## Полный релиз: сборка + архивы + тег + создание Gitea Release + загрузка файлов +## Использование: make release VERSION=2.1.0 GIT_TOKEN=<токен> +## Если GIT_TOKEN не указан — соберёт локально и создаст тег, но не загрузит в Gitea ifeq ($(OS),Windows_NT) release: - @$$v='$(VERSION)'; $$tag="v$$v"; if ($$v -eq 'dev') { Write-Host '!!! VERSION не указана. Использование: make release VERSION=X.Y.Z' -ForegroundColor Red; exit 1 }; $$existing = git tag -l $$tag; if ($$existing) { Write-Host "!!! Тег $$tag уже существует. Удалите: make release-tag-delete VERSION=$$v" -ForegroundColor Red; exit 1 }; $$dirty = git status --porcelain; if ($$dirty) { Write-Host '!!! Есть незакоммиченные изменения. Сначала сделайте git commit.' -ForegroundColor Red; git status --short; exit 1 }; Write-Host ""; Write-Host "══════════════════════════════════════" -ForegroundColor Cyan; Write-Host " Создание релиза $$tag" -ForegroundColor Cyan; Write-Host "══════════════════════════════════════" -ForegroundColor Cyan; Write-Host ""; Write-Host ">>> Создание тега $$tag..." -ForegroundColor Yellow; git tag -a $$tag -m "Release $$tag"; Write-Host ">>> Push тега $$tag в origin..." -ForegroundColor Yellow; git push origin $$tag; Write-Host ""; Write-Host "══════════════════════════════════════" -ForegroundColor Green; Write-Host " Тег $$tag создан и запушен." -ForegroundColor Green; Write-Host " CI/CD pipeline запущен автоматически." -ForegroundColor Green; Write-Host " Статус: $(GITEA_URL)/$(GITEA_REPO)/actions" -ForegroundColor Green; Write-Host "══════════════════════════════════════" -ForegroundColor Green + @$$ErrorActionPreference = "Stop"; \ + $$v='$(VERSION)'; $$tag="v$$v"; $$token='$(GIT_TOKEN)'; if (-not $$token) { $$token='$(GITEA_TOKEN)' }; \ + if ($$v -eq 'dev' -or $$v -eq '') { Write-Host '!!! VERSION не указана. Использование: make release VERSION=X.Y.Z' -ForegroundColor Red; exit 1 }; \ + $$dirty = git status --porcelain; \ + if ($$dirty) { Write-Host '!!! Есть незакоммиченные изменения. Сначала сделайте git commit.' -ForegroundColor Red; git status --short; exit 1 }; \ + Write-Host ""; \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan; \ + Write-Host " Создание релиза $$tag" -ForegroundColor Cyan; \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Cyan; \ + Write-Host ""; \ + Write-Host ">>> [1/6] Кросс-компиляция для всех платформ..." -ForegroundColor Yellow; \ + $$env:GOOS=''; $$env:GOARCH=''; Remove-Item Env:GOARM,Env:GOMIPS -ErrorAction SilentlyContinue; \ + make build-all VERSION=$$v; \ + Write-Host ""; \ + Write-Host ">>> [2/6] Создание архивов..." -ForegroundColor Yellow; \ + $$archDir = '$(BUILD_DIR)\archives'; \ + New-Item -ItemType Directory -Force $$archDir | Out-Null; \ + Get-ChildItem '$(BUILD_DIR)' -File | Where-Object { $$_.Name -ne '.env' } | ForEach-Object { \ + if ($$_.Name -match '\.exe$$') { \ + $$arcName = $$_.BaseName + '.zip'; \ + Write-Host " $$($$.Name) → $$arcName"; \ + Compress-Archive -Path $$_.FullName -DestinationPath "$$archDir\$$arcName" -Force; \ + } elseif ($$_.Name -match '^$(APP_NAME)-') { \ + $$arcName = $$_.Name + '.tar.gz'; \ + Write-Host " $$($$.Name) → $$arcName"; \ + tar -czf "$$archDir\$$arcName" -C '$(BUILD_DIR)' $$_.Name; \ + } \ + }; \ + Write-Host ""; \ + Write-Host ">>> [3/6] Генерация SHA256..." -ForegroundColor Yellow; \ + $$checksumFile = "$$archDir\checksums-sha256.txt"; \ + Get-ChildItem $$archDir -File | Where-Object { $$_.Name -ne 'checksums-sha256.txt' } | Get-FileHash -Algorithm SHA256 | ForEach-Object { "$$($$_.Hash.ToLower()) $$(Split-Path $$_.Path -Leaf)" } | Out-File -Encoding UTF8 $$checksumFile; \ + Write-Host " checksums-sha256.txt создан"; \ + Write-Host ""; \ + Write-Host ">>> [4/6] Создание тега $$tag..." -ForegroundColor Yellow; \ + $$existingTag = git tag -l $$tag; \ + if ($$existingTag) { Write-Host " Тег $$tag уже существует, пропускаю создание" -ForegroundColor Gray } \ + else { git tag -a $$tag -m "Release $$tag" }; \ + git push origin $$tag 2>$$null; \ + git push origin master 2>$$null; \ + Write-Host ""; \ + if (-not $$token) { \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow; \ + Write-Host " Локальная сборка завершена." -ForegroundColor Yellow; \ + Write-Host " Для загрузки в Gitea укажите GIT_TOKEN:" -ForegroundColor Yellow; \ + Write-Host " make release VERSION=$$v GIT_TOKEN=<токен>" -ForegroundColor Yellow; \ + Write-Host " Архивы: $(BUILD_DIR)\archives\" -ForegroundColor Yellow; \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Yellow; \ + exit 0 \ + }; \ + Write-Host ">>> [5/6] Создание релиза в Gitea..." -ForegroundColor Yellow; \ + $$giteaUrl = '$(GITEA_URL)'; \ + $$repo = '$(GITEA_REPO)'; \ + $$commit = git rev-parse HEAD; \ + $$date = Get-Date -Format 'dd.MM.yyyy HH:mm UTC'; \ + $$prevTag = ''; try { $$prevTag = git describe --tags --abbrev=0 "$$tag^" 2>$$null } catch {}; \ + $$rawLog = ''; \ + if ($$prevTag) { $$rawLog = git log --pretty=format:"%s" "$$prevTag..$$tag" --no-merges 2>$$null } \ + else { $$rawLog = git log --pretty=format:"%s" --no-merges 2>$$null | Select-Object -First 50 }; \ + $$features = @(); $$fixes = @(); $$refactor = @(); $$other = @(); \ + ($$rawLog -split "`n") | Where-Object { $$_ } | ForEach-Object { \ + $$low = $$_.ToLower(); \ + if ($$low -match '^(функция|feat|feature|добавлен|реализован|новое)') { $$features += "- $$_" } \ + elseif ($$low -match '^(исправлен|fix|bugfix|баг|ошибка)') { $$fixes += "- $$_" } \ + elseif ($$low -match '^(рефакторинг|refactor|оптимизац|улучшен)') { $$refactor += "- $$_" } \ + else { $$other += "- $$_" } \ + }; \ + $$body = "## GenAudioBookInfo $$tag`n`n"; \ + if (Test-Path 'RELEASE_NOTES.md') { $$body += (Get-Content 'RELEASE_NOTES.md' -Raw) + "`n`n" }; \ + if ($$features.Count -gt 0) { $$body += "### Новые возможности`n" + ($$features -join "`n") + "`n`n" }; \ + if ($$fixes.Count -gt 0) { $$body += "### Исправления`n" + ($$fixes -join "`n") + "`n`n" }; \ + if ($$refactor.Count -gt 0) { $$body += "### Рефакторинг`n" + ($$refactor -join "`n") + "`n`n" }; \ + if ($$other.Count -gt 0) { $$body += "### Прочие изменения`n" + ($$other -join "`n") + "`n`n" }; \ + $$body += "---`n`n### Информация о сборке`n`n| Параметр | Значение |`n|---|---|`n| Коммит | ``$$commit`` |`n| Дата | $$date |`n| Go | $(GO_VERSION) |`n"; \ + $$bodyJson = $$body | ConvertTo-Json; \ + $$releaseData = @{ tag_name = $$tag; target_commitish = $$commit; name = "$(APP_NAME) $$tag"; body = $$body; draft = $$false; prerelease = $$false } | ConvertTo-Json -Compress; \ + $$headers = @{ Authorization = "token $$token"; 'Content-Type' = 'application/json' }; \ + $$releaseResp = Invoke-RestMethod -Method Post -Uri "$$giteaUrl/api/v1/repos/$$repo/releases" -Headers $$headers -Body $$releaseData -ErrorAction Stop; \ + $$releaseId = $$releaseResp.id; \ + Write-Host " Релиз создан: ID=$$releaseId"; \ + Write-Host ""; \ + Write-Host ">>> [6/6] Загрузка файлов..." -ForegroundColor Yellow; \ + $$ok = 0; $$fail = 0; \ + Get-ChildItem $$archDir -File | ForEach-Object { \ + $$fname = $$_.Name; $$fsize = '{0:N1} MB' -f ($$_.Length / 1MB); \ + Write-Host -NoNewline (" {0,-55} [{1,6}] " -f $$fname, $$fsize); \ + try { \ + $$bytes = [System.IO.File]::ReadAllBytes($$_.FullName); \ + $$uploadHeaders = @{ Authorization = "token $$token"; 'Content-Type' = 'application/octet-stream' }; \ + Invoke-RestMethod -Method Post -Uri "$$giteaUrl/api/v1/repos/$$repo/releases/$$releaseId/assets?name=$$fname" -Headers $$uploadHeaders -Body $$bytes -ErrorAction Stop | Out-Null; \ + Write-Host "OK" -ForegroundColor Green; \ + $$ok++ \ + } catch { \ + Write-Host "ОШИБКА" -ForegroundColor Red; \ + $$fail++ \ + } \ + }; \ + Write-Host ""; \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Green; \ + Write-Host " Загружено: $$ok | Ошибок: $$fail" -ForegroundColor Green; \ + Write-Host " Релиз: $$giteaUrl/$$repo/releases/tag/$$tag" -ForegroundColor Green; \ + Write-Host "══════════════════════════════════════════════" -ForegroundColor Green; \ + if ($$fail -gt 0) { exit 1 } else release: - @v="$(VERSION)"; tag="v$$v"; \ - if [ "$$v" = "dev" ]; then echo "!!! VERSION не указана. Использование: make release VERSION=X.Y.Z"; exit 1; fi; \ - if git tag -l "$$tag" | grep -q .; then echo "!!! Тег $$tag уже существует. Удалите: make release-tag-delete VERSION=$$v"; exit 1; fi; \ - if [ -n "$$(git status --porcelain)" ]; then echo "!!! Есть незакоммиченные изменения. Сначала сделайте git commit."; git status --short; exit 1; fi; \ + @v="$(VERSION)"; tag="v$$v"; token="$(GIT_TOKEN)"; [ -z "$$token" ] && token="$(GITEA_TOKEN)"; \ + if [ "$$v" = "dev" ] || [ -z "$$v" ]; then echo "!!! VERSION не указана. Использование: make release VERSION=X.Y.Z"; exit 1; fi; \ + if [ -n "$$(git status --porcelain)" ]; then echo "!!! Есть незакоммиченные изменения."; git status --short; exit 1; fi; \ echo ""; \ - echo "══════════════════════════════════════"; \ + echo "══════════════════════════════════════════════"; \ echo " Создание релиза $$tag"; \ - echo "══════════════════════════════════════"; \ + echo "══════════════════════════════════════════════"; \ echo ""; \ - echo ">>> Создание тега $$tag..."; \ - git tag -a "$$tag" -m "Release $$tag"; \ - echo ">>> Push тега $$tag в origin..."; \ - git push origin "$$tag"; \ + echo ">>> [1/6] Кросс-компиляция..."; \ + make build-all VERSION=$$v; \ echo ""; \ - echo "══════════════════════════════════════"; \ - echo " Тег $$tag создан и запушен."; \ - echo " CI/CD pipeline запущен автоматически."; \ - echo " Статус: $(GITEA_URL)/$(GITEA_REPO)/actions"; \ - echo "══════════════════════════════════════" + echo ">>> [2/6] Создание архивов..."; \ + mkdir -p $(BUILD_DIR)/archives; \ + cd $(BUILD_DIR) && for f in *; do \ + [ -f "$$f" ] || continue; \ + case "$$f" in \ + *.exe) arcname="$${f%.exe}.zip"; echo " $$f → $$arcname"; zip "archives/$$arcname" "$$f" ;; \ + $(APP_NAME)-*) arcname="$$f.tar.gz"; echo " $$f → $$arcname"; tar -czf "archives/$$arcname" "$$f" ;; \ + esac; \ + done; cd ..; \ + echo ""; \ + echo ">>> [3/6] SHA256..."; \ + cd $(BUILD_DIR)/archives && sha256sum * > checksums-sha256.txt && cd ../..; \ + echo ""; \ + echo ">>> [4/6] Создание тега $$tag..."; \ + if git tag -l "$$tag" | grep -q .; then echo " Тег $$tag уже существует, пропускаю"; \ + else git tag -a "$$tag" -m "Release $$tag"; fi; \ + git push origin "$$tag" 2>/dev/null || true; \ + git push origin master 2>/dev/null || true; \ + echo ""; \ + if [ -z "$$token" ]; then \ + echo "══════════════════════════════════════════════"; \ + echo " Локальная сборка завершена."; \ + echo " Для загрузки в Gitea укажите GIT_TOKEN:"; \ + echo " make release VERSION=$$v GIT_TOKEN=<токен>"; \ + echo " Архивы: $(BUILD_DIR)/archives/"; \ + echo "══════════════════════════════════════════════"; \ + exit 0; \ + fi; \ + echo ">>> [5/6] Создание релиза в Gitea..."; \ + GITEA_URL="$(GITEA_URL)"; REPO="$(GITEA_REPO)"; \ + COMMIT=$$(git rev-parse HEAD); \ + DATE=$$(date -u '+%d.%m.%Y %H:%M UTC'); \ + PREV_TAG=$$(git describe --tags --abbrev=0 "$$tag^" 2>/dev/null || echo ""); \ + if [ -n "$$PREV_TAG" ]; then RAW_LOG=$$(git log --pretty=format:"%s" "$$PREV_TAG..$$tag" --no-merges 2>/dev/null); \ + else RAW_LOG=$$(git log --pretty=format:"%s" --no-merges | head -50); fi; \ + FEATS=""; FIXES=""; REFAC=""; OTHER=""; \ + echo "$$RAW_LOG" | while IFS= read -r line; do \ + [ -z "$$line" ] && continue; \ + low=$$(echo "$$line" | tr '[:upper:]' '[:lower:]'); \ + case "$$low" in \ + функция:*|feat:*|feature:*|добавлен*|реализован*|новое:*) FEATS="$$FEATS\n- $$line" ;; \ + исправлен*|fix:*|bugfix:*) FIXES="$$FIXES\n- $$line" ;; \ + рефакторинг:*|refactor:*|оптимизац*|улучшен*) REFAC="$$REFAC\n- $$line" ;; \ + *) OTHER="$$OTHER\n- $$line" ;; \ + esac; \ + done; \ + BODY="## GenAudioBookInfo $$tag\n\n"; \ + [ -f RELEASE_NOTES.md ] && BODY="$$BODY$$(cat RELEASE_NOTES.md)\n\n"; \ + BODY="$$BODY---\n\n### Информация о сборке\n\n| Параметр | Значение |\n|---|---|\n| Коммит | \`$$COMMIT\` |\n| Дата | $$DATE |\n"; \ + BODY_JSON=$$(printf '%s' "$$BODY" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'); \ + RELEASE_JSON=$$(curl -sf -X POST \ + -H "Authorization: token $$token" \ + -H "Content-Type: application/json" \ + "$$GITEA_URL/api/v1/repos/$$REPO/releases" \ + -d "{\"tag_name\":\"$$tag\",\"target_commitish\":\"$$COMMIT\",\"name\":\"$(APP_NAME) $$tag\",\"body\":$$BODY_JSON,\"draft\":false,\"prerelease\":false}"); \ + RELEASE_ID=$$(echo "$$RELEASE_JSON" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null); \ + if [ -z "$$RELEASE_ID" ] || [ "$$RELEASE_ID" = "None" ]; then echo "ОШИБКА: не удалось создать релиз!"; echo "$$RELEASE_JSON"; exit 1; fi; \ + echo " Релиз создан: ID=$$RELEASE_ID"; \ + echo ""; \ + echo ">>> [6/6] Загрузка файлов..."; \ + OK=0; FAIL=0; \ + for FILE in $(BUILD_DIR)/archives/*; do \ + [ -f "$$FILE" ] || continue; \ + FNAME=$$(basename "$$FILE"); FSIZE=$$(du -sh "$$FILE" | cut -f1); \ + printf " %-55s [%5s] " "$$FNAME" "$$FSIZE"; \ + HTTP=$$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token $$token" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"$$FILE" \ + "$$GITEA_URL/api/v1/repos/$$REPO/releases/$$RELEASE_ID/assets?name=$$FNAME"); \ + if [ "$$HTTP" -ge 200 ] && [ "$$HTTP" -lt 300 ]; then echo "OK"; OK=$$((OK+1)); \ + else echo "ОШИБКА (HTTP $$HTTP)"; FAIL=$$((FAIL+1)); fi; \ + done; \ + echo ""; \ + echo "══════════════════════════════════════════════"; \ + echo " Загружено: $$OK | Ошибок: $$FAIL"; \ + echo " Релиз: $$GITEA_URL/$$REPO/releases/tag/$$tag"; \ + echo "══════════════════════════════════════════════"; \ + [ "$$FAIL" -gt 0 ] && exit 1; true endif ## Проверить git-статус перед созданием тега @@ -617,12 +793,13 @@ help: $(info make build-openbsd OpenBSD: amd64) $(info make build-netbsd NetBSD: amd64) $(info ) - $(info Релиз (CI/CD):) - $(info make release VERSION=X.Y.Z Полный релиз: тег + push → CI собирает и публикует) - $(info make ci-check Проверить git-статус перед релизом) - $(info make release-tag-delete VERSION=X.Y.Z Удалить ошибочный тег) + $(info Релиз:) + $(info make release VERSION=X.Y.Z GIT_TOKEN=xxx Полный релиз: сборка + тег + публикация в Gitea) + $(info make release VERSION=X.Y.Z Только локальная сборка без загрузки) + $(info make release-tag-delete VERSION=X.Y.Z Удалить ошибочный тег) + $(info make ci-check Проверить git-статус перед релизом) $(info ) - $(info Локальная сборка:) + $(info Дополнительно:) $(info make release-local Все платформы + архивы + SHA256 → build/release/) $(info make checksum Сборка + SHA256 контрольные суммы) $(info )