Skip to content

Build and Release

Build and Release #4

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: 1
- name: Get version
id: get_version
shell: pwsh
run: |
if ($env:GITHUB_REF -like 'refs/tags/*') {
# 已由 tag 推送触发,直接使用该 tag 作为版本
$version = $env:GITHUB_REF.Substring(10)
$tag = $version
} else {
# 手动 workflow_dispatch 触发:使用 git 的语义化版本排序,选出最新 tag,然后生成 / 递增 beta 版本
# --refs 可避免出现带 ^{} 的 peeled 行,--sort=-version:refname 以版本名倒序排序,'v*' 仅匹配语义化版本前缀
$latestRef = git ls-remote --refs --tags --sort=-version:refname origin 'v*' |
Where-Object { $_ -match 'refs/tags/(v\d+\.\d+\.\d+(?:-beta\.\d+)?)$' } |
Select-Object -First 1
if (-not $latestRef) {
# 仓库还没有任何符合语义版本的 tag,初始化为 v0.1.0-beta.1
$baseVersion = 'v0.1.0'
$betaNumber = 1
$tag = "$baseVersion-beta.$betaNumber"
} else {
# 行格式: <hash>\trefs/tags/<tagname>
if ($latestRef -match 'refs/tags/(.+)$') {
$latestTag = $matches[1]
} else {
throw "无法从 ls-remote 输出中解析最新 tag:$latestRef"
}
if ($latestTag -match '^(v\d+\.\d+\.\d+)-beta\.(\d+)$') {
# 最新即为 beta,直接在 beta 编号上 +1
$baseVersion = $matches[1]
$betaNumber = [int]$matches[2] + 1
$tag = "$baseVersion-beta.$betaNumber"
} else {
# 最新为稳定版本,从该稳定版本开始新的 beta 序列
$latestTag -match '^(v\d+\.\d+\.)(\d+)$'
$baseVersion = $matches[1] + ([int]$matches[2] + 1)
$betaNumber = 1
$tag = "$baseVersion-beta.$betaNumber"
}
}
$version = $tag
git config --global user.email "actions@github.com"
git config --global user.name "GitHub Actions"
# 创建并推送新 beta tag
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 | Invoke-Expression
- 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 --group dev
- 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
Remove-Item -Path $zipPath -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 > deploy/dist/requirements.txt
pip wheel --wheel-dir=deploy/dist/wheels -r deploy/dist/requirements.txt
# 压缩生成的 wheels 目录
Compress-Archive -Path deploy/dist/wheels/* -DestinationPath deploy/dist/StarRail-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/StarRail-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: |
# 将 dist 移动到当前目录的父目录下
$distDir = "deploy/dist"
$parentDir = Split-Path -Path (Get-Location) -Parent
$targetDist = Join-Path $parentDir "dist"
Move-Item -Path $distDir -Destination $targetDist -Force
$distDir = $targetDist
$envDir = ".install"
New-Item -ItemType Directory -Path $envDir -Force | Out-Null
# 准备离线运行所需的 .install 资源
Invoke-WebRequest -Uri "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/StarRail-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/StarRail-OneDragon/MinGit.zip" -OutFile "$envDir/MinGit.zip"
Invoke-WebRequest -Uri "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/StarRail-OneDragon/cpython-3.11.zip" -OutFile "$envDir/cpython-3.11.zip"
# 将安装器复制到仓库根目录
Copy-Item "$distDir/OneDragon-Installer.exe" -Destination "OneDragon-Installer.exe" -Force
# 打包启动器
Compress-Archive -Path "$distDir/OneDragon-Launcher.exe" -DestinationPath "$distDir/StarRail-OneDragon-Launcher.zip" -Force
Copy-Item "$distDir/OneDragon-Launcher.exe" -Destination "OneDragon-Launcher.exe" -Force
# 模型目录(放在仓库根 assets/models 下)
$modelBase = "assets/models"
New-Item -ItemType Directory -Path "$modelBase/onnx_ocr" -Force | Out-Null
New-Item -ItemType Directory -Path "$modelBase/sim_uni" -Force | Out-Null
New-Item -ItemType Directory -Path "$modelBase/world_patrol" -Force | Out-Null
# 临时模型目录
$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
}
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
}
# 获取 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"
}
# 获取 sim uni 模型
$simUniModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-YOLO" -pattern "sim_uni.*\.zip$"
if ($simUniModel) {
$simUniName = $simUniModel.name -replace "\.zip$", ""
Write-Host "找到 sim uni 模型: $($simUniModel.name) (版本: $($simUniModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $simUniModel.url `
-downloadPath "$tempDir/sim_uni.zip" `
-destRoot "$modelBase/sim_uni" `
-folderName $simUniName
} else {
Write-Warning "无法找到 sim_uni 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-YOLO/releases/download/sr_model/yolov8n-640-simuni-0601.zip" `
-downloadPath "$tempDir/sim_uni.zip" `
-destRoot "$modelBase/sim_uni" `
-folderName "yolov8n-640-simuni-0601"
}
# 获取 world_patrol 模型
$worldPatrolModel = Get-LatestModelByNumber -repo "OneDragon-Anything/OneDragon-YOLO" -pattern "world_patrol.*\.zip$"
if ($worldPatrolModel) {
$worldPatrolName = $worldPatrolModel.name -replace "\.zip$", ""
Write-Host "找到 world patrol 模型: $($worldPatrolModel.name) (版本: $($worldPatrolModel.version_number))"
Expand-ZipIntoNamedFolder `
-url $worldPatrolModel.url `
-downloadPath "$tempDir/world_patrol.zip" `
-destRoot "$modelBase/world_patrol" `
-folderName $worldPatrolName
} else {
Write-Warning "无法找到 world patrol 模型,使用备用版本"
Expand-ZipIntoNamedFolder `
-url "https://github.com/OneDragon-Anything/OneDragon-YOLO/releases/download/sr_model/yolov8n-640-simuni-0601.zip" `
-downloadPath "$tempDir/world_patrol.zip" `
-destRoot "$modelBase/world_patrol" `
-folderName "yolov8n-640-simuni-0601"
}
# 获取版本号
$version = "${{ steps.get_version.outputs.version }}"
# 打包前删除不需要的目录
Remove-Item -Path "deploy/build" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path ".venv" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path ".install/uv_cache" -Recurse -Force -ErrorAction SilentlyContinue
git reset --hard HEAD
git clean -fd | Out-Null
# 第一次打包(Full)枚举根目录全部项目
$items = Get-ChildItem -Force | ForEach-Object { $_.FullName }
Compress-Archive -Path $items -DestinationPath "$distDir/StarRail-OneDragon-$version-Full.zip" -Force
# 将环境依赖包放入 .install 后进行第二次打包(Full-Environment)
Copy-Item "$distDir/StarRail-OneDragon-Environment.zip" -Destination "$envDir/StarRail-OneDragon-Environment.zip" -Force
$items = Get-ChildItem -Force | ForEach-Object { $_.FullName }
Compress-Archive -Path $items -DestinationPath "$distDir/StarRail-OneDragon-$version-Full-Environment.zip" -Force
# 复制安装器
Copy-Item "OneDragon-Installer.exe" -Destination "StarRail-OneDragon-$version-Installer.exe" -Force
# 将所有待发布文件移到当前目录
Move-Item -Path "$distDir/*.zip" -Destination (Get-Location) -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: |
# 安装方式
- `StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Full-Environment.zip` 为带环境的完整包,不需要额外下载资源。
- `StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Full.zip` 为完整包,解压后选择解压目录为安装目录,只需要下载环境依赖。
- `StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Installer.exe` 为精简安装程序,运行后会自动下载所需的资源。
- 如果你想更新启动器,前往主程序【设置】-【资源下载】页面更新,或者下载 `StarRail-OneDragon-Launcher.zip`,解压后替换。
- __不要下载Source Code__
安装前请查看 [安装指南](https://onedragon-anything.github.io/sr/zh/quickstart.html)
若运行出错请查看 [自助排障指南](https://www.kdocs.cn/l/cbSJUUNotJ3Z)
# 更新内容
${{ steps.changelog.outputs.clean_changelog }}
files: |
StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Full-Environment.zip
StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Full.zip
StarRail-OneDragon-${{ steps.get_version.outputs.version }}-Installer.exe
StarRail-OneDragon-Environment.zip
StarRail-OneDragon-Launcher.zip
generate_release_notes: false
prerelease: ${{ contains(steps.get_version.outputs.tag, '-beta.') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}