Build and Release #33
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| create_release: | |
| description: 'Create Release' | |
| required: true | |
| default: false | |
| type: boolean | |
| env: | |
| CREATE_RELEASE: ${{ (github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/') }} | |
| jobs: | |
| build: | |
| runs-on: windows-latest | |
| outputs: | |
| version: ${{ steps.get_version.outputs.version }} | |
| tag: ${{ steps.get_version.outputs.tag }} | |
| unsigned_artifact_id: ${{ steps.upload-unsigned-artifact.outputs.artifact-id }} | |
| steps: | |
| - name: Checkout code | |
| 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/ZenlessZoneZero-OneDragon-Environment.zip | |
| - name: Upload Installer | |
| if: ${{ env.CREATE_RELEASE == 'false' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Installer | |
| path: deploy/dist/OneDragon-Installer.exe | |
| - name: Upload Launcher | |
| if: ${{ env.CREATE_RELEASE == 'false' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Launcher | |
| path: deploy/dist/OneDragon-Launcher.exe | |
| - name: Upload Wheels | |
| if: ${{ env.CREATE_RELEASE == 'false' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: Wheels | |
| path: deploy/dist/ZenlessZoneZero-OneDragon-Environment.zip | |
| - name: Upload Dist | |
| if: ${{ env.CREATE_RELEASE == 'true' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| if-no-files-found: error | |
| path: deploy/dist | |
| - name: Upload Unsigned Artifact | |
| if: ${{ env.CREATE_RELEASE == 'true' }} | |
| id: upload-unsigned-artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # 上传后是 unsigned.zip | |
| name: unsigned | |
| if-no-files-found: error | |
| path: | | |
| .\deploy\dist\OneDragon-Launcher.exe | |
| .\deploy\dist\OneDragon-Installer.exe | |
| sign: | |
| runs-on: windows-latest | |
| needs: build | |
| if: ${{ (github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/') }} | |
| env: | |
| SIGNED_DIR: 'signed' | |
| SIGNPATH_SIGNING_POLICY_SLUG: 'release-signing' | |
| steps: | |
| - name: Sign Artifact | |
| if: ${{ needs.build.outputs.unsigned_artifact_id != '' }} | |
| uses: signpath/github-action-submit-signing-request@v1.1 | |
| with: | |
| api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' | |
| organization-id: '${{ vars.SIGNPATH_ORGANIZATION_ID }}' | |
| project-slug: 'ZenlessZoneZero-OneDragon' | |
| signing-policy-slug: '${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}' | |
| github-artifact-id: "${{ needs.build.outputs.unsigned_artifact_id }}" | |
| wait-for-completion: true | |
| # 签名后会自动下载到这个目录并解压 文件名和原来的一样 | |
| output-artifact-directory: '${{ env.SIGNED_DIR }}' | |
| - name: Upload signed executables | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: signed | |
| if-no-files-found: error | |
| path: ${{ env.SIGNED_DIR }} | |
| release: | |
| runs-on: windows-latest | |
| needs: | |
| - build | |
| - sign | |
| if: ${{ ((github.event_name == 'workflow_dispatch' && inputs.create_release == true) || startsWith(github.ref, 'refs/tags/')) && needs.sign.result == 'success' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Download dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: deploy/dist | |
| - name: Download signed executables | |
| if: ${{ needs.sign.result == 'success' }} | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: signed | |
| path: deploy/dist/signed | |
| - name: Replace unsigned executables with signed ones | |
| if: ${{ needs.sign.result == 'success' }} | |
| shell: pwsh | |
| run: | | |
| Copy-Item "deploy/dist/signed/OneDragon-Installer.exe" -Destination "deploy/dist/OneDragon-Installer.exe" -Force | |
| Copy-Item "deploy/dist/signed/OneDragon-Launcher.exe" -Destination "deploy/dist/OneDragon-Launcher.exe" -Force | |
| - name: Prepare release directory and models | |
| shell: pwsh | |
| env: | |
| RELEASE_VERSION: ${{ needs.build.outputs.version }} | |
| 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/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" | |
| Invoke-WebRequest -Uri "https://github.com/OneDragon-Anything/OneDragon-Env/releases/download/ZenlessZoneZero-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/ZenlessZoneZero-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/flash_classifier" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$modelBase/hollow_zero_event" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$modelBase/lost_void_det" -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" | |
| } | |
| # 获取 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 = $env:RELEASE_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/ZenlessZoneZero-OneDragon-$version-Full.zip" -Force | |
| # 将环境依赖包放入 .install 后进行第二次打包(Full-Environment) | |
| Copy-Item "$distDir/ZenlessZoneZero-OneDragon-Environment.zip" -Destination "$envDir/ZenlessZoneZero-OneDragon-Environment.zip" -Force | |
| $items = Get-ChildItem -Force | ForEach-Object { $_.FullName } | |
| Compress-Archive -Path $items -DestinationPath "$distDir/ZenlessZoneZero-OneDragon-$version-Full-Environment.zip" -Force | |
| # 复制安装器 | |
| Copy-Item "OneDragon-Installer.exe" -Destination "ZenlessZoneZero-OneDragon-$version-Installer.exe" -Force | |
| # 将所有待发布文件移到当前目录 | |
| Move-Item -Path "$distDir/*.zip" -Destination (Get-Location) -Force | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ needs.build.outputs.tag }} | |
| name: "Release ${{ needs.build.outputs.version }}" | |
| body: | | |
| ## 安装方式 | |
| - `ZenlessZoneZero-OneDragon-${{ needs.build.outputs.version }}-Full-Environment.zip` 为带环境的完整包,不需要额外下载资源。 | |
| - `ZenlessZoneZero-OneDragon-${{ needs.build.outputs.version }}-Full.zip` 为完整包,解压后选择解压目录为安装目录,只需要下载环境依赖。 | |
| - `ZenlessZoneZero-OneDragon-${{ needs.build.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) | |
| 已有 Mirror酱 CDK?前往 [Mirror酱](https://mirrorchyan.com/zh/projects?rid=ZZZ-OneDragon&source=zzzgh-release) 高速下载 | |
| ## 签名策略 (Code Signing Policy) | |
| 免费代码签名由 [SignPath.io](https://signpath.io/) 提供,证书由 [SignPath Foundation](https://signpath.org/) 颁发 | |
| 审批人:[DoctorReid](https://github.com/DoctorReid) [ShadowLemoon](https://github.com/ShadowLemoon) | |
| Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) | |
| Approvers: [DoctorReid](https://github.com/DoctorReid) [ShadowLemoon](https://github.com/ShadowLemoon) | |
| ## 隐私政策 (Privacy Policy) | |
| 本程序的隐私政策详见:[隐私政策](.github/PRIVACY.md)。 | |
| See the [Privacy Policy](.github/PRIVACY.md). | |
| files: | | |
| ZenlessZoneZero-OneDragon-${{ needs.build.outputs.version }}-Full-Environment.zip | |
| ZenlessZoneZero-OneDragon-${{ needs.build.outputs.version }}-Full.zip | |
| ZenlessZoneZero-OneDragon-${{ needs.build.outputs.version }}-Installer.exe | |
| ZenlessZoneZero-OneDragon-Environment.zip | |
| ZenlessZoneZero-OneDragon-Launcher.zip | |
| generate_release_notes: false | |
| prerelease: ${{ contains(needs.build.outputs.version, '-beta.') }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |