Skip to content

Build and Release

Build and Release #12

Workflow file for this run

name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
create_release:
description: 'Create a release with current build'
required: true
default: false
type: boolean
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version
id: get_version
shell: pwsh
run: |
if ($env:GITHUB_REF -like 'refs/tags/*') {
$version = $env:GITHUB_REF.Substring(10)
$tag = $version
} else {
$date = (Get-Date).ToUniversalTime().AddHours(8).ToString('yyyy.MMdd.HHmm')
$tag = "v$date"
$version = $tag
git config --global user.email "actions@github.com"
git config --global user.name "GitHub Actions"
git tag $tag
git push origin $tag
}
echo "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
echo "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
echo "__version__ = '$version'" | Out-File -FilePath src/one_dragon/version.py -Encoding utf8
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11.9'
- name: Install uv
shell: pwsh
run: |
Invoke-WebRequest -Uri https://astral.sh/uv/install.ps1 -OutFile install.ps1
.\install.ps1
- name: Create and activate virtual environment
shell: pwsh
run: |
uv venv .venv --python=3.11.12
- name: Install dependencies
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
uv sync --all-groups
- name: Download and extract UPX into venv Scripts
shell: pwsh
run: |
$venvScripts = ".\.venv\Scripts"
$upxDir = Join-Path $venvScripts "upx"
$sourceUpxPath = Join-Path $upxDir "upx-4.2.3-win64" "upx.exe"
$destinationUpxPath = Join-Path $venvScripts "upx.exe"
$zipPath = "upx.zip"
Invoke-WebRequest -Uri "https://github.com/upx/upx/releases/download/v4.2.3/upx-4.2.3-win64.zip" -OutFile $zipPath
Expand-Archive -Path $zipPath -DestinationPath $upxDir -Force
Move-Item -Path $sourceUpxPath -Destination $destinationUpxPath -Force
Remove-Item -Path $upxDir -Recurse -Force
- name: Build executables
shell: pwsh
run: |
.\.venv\Scripts\Activate.ps1
cd deploy
pyinstaller "OneDragon-Installer.spec"
pyinstaller "OneDragon-Launcher.spec"
- name: Bundle dependencies into wheels
shell: pwsh
run: |
# 激活虚拟环境
.\.venv\Scripts\Activate.ps1
# 创建目标目录并生成 wheel 包
New-Item -ItemType Directory -Path deploy/dist/wheels -Force
uv export --no-hashes --no-dev --format requirements-txt > requirements-prod.txt
pip wheel --wheel-dir=deploy/dist/wheels -r requirements-prod.txt
# 压缩生成的 wheels 目录
Compress-Archive -Path deploy/dist/wheels/* -DestinationPath deploy/dist/ZenlessZoneZero-OneDragon-Environment.zip
- name: Upload Installer
uses: actions/upload-artifact@v4
with:
name: Installer
path: deploy/dist/OneDragon-Installer.exe
- name: Upload Launcher
uses: actions/upload-artifact@v4
with:
name: Launcher
path: deploy/dist/OneDragon-Launcher.exe
- name: Upload Wheels
uses: actions/upload-artifact@v4
with:
name: Wheels
path: deploy/dist/ZenlessZoneZero-OneDragon-Environment.zip
- name: Prepare release directory and models
if: ${{ (github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/') }}
shell: pwsh
run: |
function Expand-ZipIntoNamedFolder {
param (
[string]$url,
[string]$downloadPath,
[string]$destRoot,
[string]$folderName
)
Invoke-WebRequest -Uri $url -OutFile $downloadPath
$targetPath = Join-Path $destRoot $folderName
New-Item -ItemType Directory -Path $targetPath -Force
Expand-Archive -Path $downloadPath -DestinationPath $targetPath -Force
}
$distDir = "deploy/dist"
$rootDir = "$distDir/ZenlessZoneZero-OneDragon"
New-Item -ItemType Directory -Path $rootDir -Force
# .install
$envDir = "$rootDir/.install"
New-Item -ItemType Directory -Path $envDir -Force
Invoke-WebRequest -Uri "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/ZenlessZoneZero-OneDragon/uv-x86_64-pc-windows-msvc.zip" -OutFile "$envDir/uv-x86_64-pc-windows-msvc.zip"
Invoke-WebRequest -Uri "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/ZenlessZoneZero-OneDragon/MinGit.zip" -OutFile "$envDir/MinGit.zip"
# 下载并提取Python
$pythonDir = "$envDir/python"
$pythonTargetDir = "$pythonDir/cpython-3.11.12-windows-x86_64-none"
New-Item -ItemType Directory -Path $pythonTargetDir -Force
$pythonTarGz = "$envDir/python.tar.gz"
Invoke-WebRequest -Uri "https://github.com/astral-sh/python-build-standalone/releases/download/20250517/cpython-3.11.12+20250517-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" -OutFile $pythonTarGz
# 提取 tar.gz 文件
tar -xzf $pythonTarGz -C $pythonTargetDir --strip-components=1
Remove-Item -Path $pythonTarGz -Force
# 复制安装器
Copy-Item "$distDir/OneDragon-Installer.exe" -Destination $rootDir -Force
# 打包启动器
Compress-Archive -Path "$distDir/OneDragon-Launcher.exe" -DestinationPath "$distDir/ZenlessZoneZero-OneDragon-Launcher.zip" -Force
Copy-Item "$distDir/ZenlessZoneZero-OneDragon-Launcher.zip" -Destination $envDir -Force
# 复制资源文件
Copy-Item "assets/text" -Destination "$rootDir/assets/text" -Recurse -Force
Copy-Item "assets/ui" -Destination "$rootDir/assets/ui" -Recurse -Force
# 创建模型目录
$modelBase = "$rootDir/assets/models"
New-Item -ItemType Directory -Path "$modelBase/onnx_ocr" -Force
New-Item -ItemType Directory -Path "$modelBase/flash_classifier" -Force
New-Item -ItemType Directory -Path "$modelBase/hollow_zero_event" -Force
New-Item -ItemType Directory -Path "$modelBase/lost_void_det" -Force
# 临时模型目录
$tempDir = "temp_models"
New-Item -ItemType Directory -Path $tempDir -Force
# 通过文件末尾最高8位数字获取最新模型
function Get-LatestModelByNumber {
param (
[string]$repo,
[string]$pattern
)
$apiUrl = "https://api.github.com/repos/$repo/releases"
$releases = Invoke-RestMethod -Uri $apiUrl -Headers @{ "Accept" = "application/vnd.github.v3+json" }
$bestAsset = $null
$maxNumber = -1
foreach ($release in $releases) {
foreach ($asset in $release.assets) {
if ($asset.name -match $pattern) {
# 从文件名末尾提取8位数字(在.zip之前)
if ($asset.name -match '(\d{8})\.zip$') {
$number = [int]$matches[1]
if ($number -gt $maxNumber) {
$maxNumber = $number
$bestAsset = @{
url = $asset.browser_download_url
name = $asset.name
version_number = $number
}
}
}
# 对于ppocrv5和其他没有8位数字后缀的模型,作为备用选项
elseif ($bestAsset -eq $null) {
$bestAsset = @{
url = $asset.browser_download_url
name = $asset.name
version_number = 0
}
}
}
}
}
return $bestAsset
}
# 通过最高版本号获取最新模型
Write-Host "正在获取最新模型信息..."
# 获取 ppocrv5 模型 (onnx_ocr)
$ppocrModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-Env" -pattern "ppocrv5\.zip$"
if ($ppocrModel) {
$ppocrName = $ppocrModel.name -replace "\.zip$", ""
Write-Host "找到 ppocrv5 模型: $($ppocrModel.name) (版本: $($ppocrModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $ppocrModel.url `
-downloadPath "$tempDir/ppocrv5.zip" `
-destRoot "$modelBase/onnx_ocr" `
-folderName $ppocrName
} else {
Write-Warning "无法找到 ppocrv5 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/ppocrv5/ppocrv5.zip" `
-downloadPath "$tempDir/ppocrv5.zip" `
-destRoot "$modelBase/onnx_ocr" `
-folderName "ppocrv5"
}
# 获取 flash classifier 模型
$flashModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-YOLO" -pattern "flash.*\.zip$"
if ($flashModel) {
$flashName = $flashModel.name -replace "\.zip$", ""
Write-Host "找到 flash 模型: $($flashModel.name) (版本: $($flashModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $flashModel.url `
-downloadPath "$tempDir/flash.zip" `
-destRoot "$modelBase/flash_classifier" `
-folderName $flashName
} else {
Write-Warning "无法找到 flash 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-YOLO/releases/download/zzz_model/yolov8n-640-flash-0127.zip" `
-downloadPath "$tempDir/flash.zip" `
-destRoot "$modelBase/flash_classifier" `
-folderName "yolov8n-640-flash-0127"
}
# 获取 hollow zero event 模型
$hollowModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-YOLO" -pattern "hollow.*\.zip$"
if ($hollowModel) {
$hollowName = $hollowModel.name -replace "\.zip$", ""
Write-Host "找到 hollow 模型: $($hollowModel.name) (版本: $($hollowModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $hollowModel.url `
-downloadPath "$tempDir/hollow.zip" `
-destRoot "$modelBase/hollow_zero_event" `
-folderName $hollowName
} else {
Write-Warning "无法找到 hollow 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-YOLO/releases/download/zzz_model/yolov8s-736-hollow-zero-event-0126.zip" `
-downloadPath "$tempDir/hollow.zip" `
-destRoot "$modelBase/hollow_zero_event" `
-folderName "yolov8s-736-hollow-zero-event-0126"
}
# 获取 lost void detection 模型
$lostModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-YOLO" -pattern "lost.*\.zip$"
if ($lostModel) {
$lostName = $lostModel.name -replace "\.zip$", ""
Write-Host "找到 lost void 模型: $($lostModel.name) (版本: $($lostModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $lostModel.url `
-downloadPath "$tempDir/lost.zip" `
-destRoot "$modelBase/lost_void_det" `
-folderName $lostName
} else {
Write-Warning "无法找到 lost void 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-YOLO/releases/download/zzz_model/yolov8n-736-lost-void-det-20250612.zip" `
-downloadPath "$tempDir/lost.zip" `
-destRoot "$modelBase/lost_void_det" `
-folderName "yolov8n-736-lost-void-det-20250612"
}
# 获取版本号
$version = "${{ steps.get_version.outputs.version }}"
# 打包完整版本
Compress-Archive -Path "$rootDir/*" -DestinationPath "$distDir/ZenlessZoneZero-OneDragon-$version-Full.zip" -Force
# 复制环境依赖到 .install 目录
Copy-Item "$distDir/ZenlessZoneZero-OneDragon-Environment.zip" -Destination "$rootDir/.install/ZenlessZoneZero-OneDragon-Environment.zip" -Force
# 打包带环境的完整版本
Compress-Archive -Path "$rootDir/*" -DestinationPath "$distDir/ZenlessZoneZero-OneDragon-$version-Full-Environment.zip" -Force
# 复制安装程序
Copy-Item "$rootDir/OneDragon-Installer.exe" -Destination "$distDir/ZenlessZoneZero-OneDragon-$version-Installer.exe" -Force
- name: Generate Changelog
id: changelog
if: ${{ (github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/') }}
shell: pwsh
run: |
$current_version_tag = "${{ steps.get_version.outputs.tag }}" # 从 get_version 步骤获取的标签
$commits = @()
if ($env:GITHUB_REF -like 'refs/tags/*') {
# 这是由标签触发的发布
# 尝试查找当前标签提交的父提交上的最新标签
$previous_tag_candidate = $(git describe --tags --abbrev=0 "$current_version_tag^" 2>$null)
if ($previous_tag_candidate) {
Write-Host "正在生成从 $previous_tag_candidate 到 $current_version_tag 的更新日志"
$commits = git log --pretty=format:"%s|%h" "$previous_tag_candidate..$current_version_tag"
} else {
Write-Host "未找到相对于 $current_version_tag^ 的上一个标签。列出 $current_version_tag 的最后10个提交。"
# 这可能是第一个标签。列出导致它的提交。
$commits = git log --pretty=format:"%s|%h" -n 10 "$current_version_tag"
}
} else {
# 这是手动 workflow_dispatch 或分支推送
Write-Host "手动或分支构建。列出 HEAD 的最后5个提交。"
$commits = git log --pretty=format:"%s|%h" -n 5 HEAD
}
# 按前缀分类提交
$categories = @{
"feat" = @()
"fix" = @()
"perf" = @()
"refactor" = @()
"style" = @()
"docs" = @()
"test" = @()
"ci" = @()
"build" = @()
"chore" = @()
"revert" = @()
"other" = @()
}
foreach ($commit in $commits) {
if ($commit -match "^(.+)\|(.+)$") {
$message = $matches[1]
$hash = $matches[2]
# 提取冒号前的前缀
if ($message -match "^([^:]+):(.*)$") {
$prefix = $matches[1].Trim().ToLower()
$content = $matches[2].Trim()
# 检查前缀是否在已知分类中
if ($categories.ContainsKey($prefix)) {
$categories[$prefix] += "- $content ($hash)"
} else {
$categories["other"] += "- $message ($hash)"
}
} else {
$categories["other"] += "- $message ($hash)"
}
}
}
# 生成分类后的更新日志
$changelog_content = ""
$category_names = @{
"feat" = "✨ 新功能"
"fix" = "🐛 Bug 修复"
"perf" = "⚡ 性能优化"
"refactor" = "♻️ 代码重构"
"style" = "💄 样式调整"
"docs" = "📄 文档更新"
"test" = "✅ 测试"
"ci" = "👷 CI/CD"
"build" = "📦 构建"
"chore" = "🔧 杂项"
"revert" = "⏪ 回滚"
"other" = "📝 其他更改"
}
# 定义输出顺序
$ordered_keys = @("feat", "fix", "perf", "refactor", "style", "docs", "test", "ci", "build", "chore", "revert", "other")
foreach ($key in $ordered_keys) {
if ($categories[$key].Count -gt 0) {
if ($changelog_content -ne "") {
$changelog_content += "`n`n"
}
$changelog_content += "## $($category_names[$key])`n"
$changelog_content += ($categories[$key] -join "`n")
}
}
if ([string]::IsNullOrWhiteSpace($changelog_content)) {
$changelog_content = "没有新的更改或无法确定更新日志。"
}
$output_name = "clean_changelog"
$delimiter = "CHANGELOG_DELIMITER_$(New-Guid)"
echo "$output_name<<$delimiter" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
echo $changelog_content | Out-File -FilePath $env:GITHUB_OUTPUT -Append
echo $delimiter | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Create Release
if: ${{ (github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/') }}
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.get_version.outputs.tag }}
name: "Release ${{ steps.get_version.outputs.version }}"
body: |
# 安装方式
- `ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Full-Environment.zip` 为带环境的完整包,不需要额外下载资源。
- `ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Full.zip` 为完整包,解压后选择解压目录为安装目录,只需要下载环境依赖。
- `ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Installer.exe` 为精简安装程序,运行后会自动下载所需的资源。
- 如果你想更新启动器,前往主程序【设置】-【资源下载】页面更新,或者下载 `ZenlessZoneZero-OneDragon-Launcher.zip`,解压后替换。
- __不要下载Source Code__
安装前请查看 [安装指南](https://one-dragon.com/zzz/zh/quickstart.html)
若运行出错请查看 [自助排障指南](https://docs.qq.com/doc/p/7add96a4600d363b75d2df83bb2635a7c6a969b5)
# 更新内容
${{ steps.changelog.outputs.clean_changelog }}
files: |
deploy/dist/ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Full-Environment.zip
deploy/dist/ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Full.zip
deploy/dist/ZenlessZoneZero-OneDragon-${{ steps.get_version.outputs.version }}-Installer.exe
deploy/dist/ZenlessZoneZero-OneDragon-Environment.zip
deploy/dist/ZenlessZoneZero-OneDragon-Launcher.zip
generate_release_notes: false
prerelease: ${{ github.event_name == 'workflow_dispatch' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}