Skip to content

fix: protect default API preset from deletion #290

fix: protect default API preset from deletion

fix: protect default API preset from deletion #290

name: Build, Release, and Publish Updates
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: read
jobs:
build-portable:
runs-on: windows-latest
name: Build ${{ matrix.variant }} Portable Package
strategy:
fail-fast: false
matrix:
include:
- variant: cpu
torch-index: https://download.pytorch.org/whl/cpu
- variant: gpu
torch-index: https://download.pytorch.org/whl/cu130
- variant: amd
torch-index: https://pypi.org/simple
steps:
- uses: actions/checkout@v4
- name: Prepare portable package from reference release
env:
GH_TOKEN: ${{ github.token }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$archive = Join-Path $env:RUNNER_TEMP 'manga-translator-ui-portable.7z'
$extractDir = Join-Path $env:GITHUB_WORKSPACE 'portable'
gh release download portable --repo hgmzhn/manga-translator-ui --pattern manga-translator-ui-portable.7z --output $archive --clobber
if ($LASTEXITCODE -ne 0) { throw 'Failed to download the portable reference package.' }
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
7z x $archive "-o$extractDir" -y
if ($LASTEXITCODE -ne 0) { throw 'Failed to extract the portable reference package.' }
Remove-Item $archive -Force
$pythonCandidates = @(
Get-ChildItem -LiteralPath $extractDir -Filter python.exe -File -Recurse |
Where-Object { $_.FullName -match '[\\/]packaging[\\/]python[\\/]python\.exe$' }
)
if ($pythonCandidates.Count -ne 1) {
throw "Expected exactly one portable Python, found $($pythonCandidates.Count)."
}
$portableDir = $pythonCandidates[0].Directory.Parent.Parent.FullName
if (-not (Test-Path (Join-Path $portableDir 'Win-Start.bat'))) {
throw "Portable root was not found next to $($pythonCandidates[0].FullName)."
}
$excluded = @('.git', '.github', '.venv', '.venv_cpu', '.venv_gpu', 'build', 'dist', 'models', 'models_backup', 'result', 'logs', 'scripts', 'test', 'reference-code', 'PortableGit', 'uploads', 'upload-cache', 'originals', 'manga_translator_work', 'memory_profiles', '.idea', '.vscode')
Get-ChildItem -LiteralPath $env:GITHUB_WORKSPACE -Force |
Where-Object { $_.Name -notin $excluded -and $_.Name -ne 'portable' } |
ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $portableDir -Recurse -Force }
Remove-Item (Join-Path $portableDir '.git') -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item -LiteralPath (Join-Path $env:GITHUB_WORKSPACE '.git') -Destination (Join-Path $portableDir '.git') -Recurse -Force
if (-not (Test-Path (Join-Path $portableDir 'packaging\python\python.exe'))) { throw 'Portable Python was not found in the reference package.' }
if (-not (Test-Path (Join-Path $portableDir 'packaging\uv.exe'))) { throw 'Portable uv was not found in the reference package.' }
Set-Content -LiteralPath (Join-Path $portableDir 'VERSION') -Value ('${{ github.ref_name }}' -replace '^v', '') -Encoding utf8
Set-Content -LiteralPath (Join-Path $portableDir 'packaging\VERSION') -Value ('${{ github.ref_name }}' -replace '^v', '') -Encoding utf8
"PORTABLE_DIR=$portableDir" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Install ${{ matrix.variant }} dependencies into bundled Python
if: ${{ matrix.variant != 'amd' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$uv = Join-Path $env:PORTABLE_DIR 'packaging\uv.exe'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
$requirements = 'requirements-portable-${{ matrix.variant }}.txt'
& $uv export --locked --no-default-groups --group '${{ matrix.variant }}' --format requirements.txt --output-file $requirements
if ($LASTEXITCODE -ne 0) { throw 'Failed to export locked dependencies.' }
& $uv pip sync --python $python --index '${{ matrix.torch-index }}' --index-strategy unsafe-best-match $requirements
if ($LASTEXITCODE -ne 0) { throw 'Failed to install dependencies into bundled Python.' }
- name: Install AMD base dependencies into bundled Python
if: ${{ matrix.variant == 'amd' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$uv = Join-Path $env:PORTABLE_DIR 'packaging\uv.exe'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
$requirements = 'requirements-portable-amd.txt'
& $uv export --locked --no-default-groups --group amd --no-emit-package torch --no-emit-package torchvision --format requirements.txt --output-file $requirements
if ($LASTEXITCODE -ne 0) { throw 'Failed to export locked AMD base dependencies.' }
& $uv pip install --python $python --no-deps --requirement $requirements
if ($LASTEXITCODE -ne 0) { throw 'Failed to install AMD base dependencies into bundled Python.' }
- name: Install Windows AMD ROCm runtime
if: ${{ matrix.variant == 'amd' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
& $python -m pip uninstall torch torchvision torchaudio -y
if ($LASTEXITCODE -ne 0) { throw 'Failed to remove the non-ROCm PyTorch runtime.' }
& $python -m pip install --no-cache-dir `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/rocm_sdk_core-7.2.1-py3-none-win_amd64.whl' `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/rocm_sdk_devel-7.2.1-py3-none-win_amd64.whl' `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/rocm_sdk_libraries_custom-7.2.1-py3-none-win_amd64.whl' `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/rocm-7.2.1.tar.gz'
if ($LASTEXITCODE -ne 0) { throw 'Failed to install the AMD ROCm SDK.' }
& $python -m pip install --no-cache-dir `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/torch-2.9.1%2Brocm7.2.1-cp312-cp312-win_amd64.whl' `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/torchaudio-2.9.1%2Brocm7.2.1-cp312-cp312-win_amd64.whl' `
'https://repo.radeon.com/rocm/windows/rocm-rel-7.2.1/torchvision-0.24.1%2Brocm7.2.1-cp312-cp312-win_amd64.whl'
if ($LASTEXITCODE -ne 0) { throw 'Failed to install the AMD ROCm PyTorch runtime.' }
- name: Clean dependency caches
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$uv = Join-Path $env:PORTABLE_DIR 'packaging\uv.exe'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
& $uv cache clean
if ($LASTEXITCODE -ne 0) { throw 'Failed to clean the uv cache.' }
& $python -m pip cache purge
if ($LASTEXITCODE -ne 0) { throw 'Failed to clean the pip cache.' }
- name: Install model files into portable package
env:
GH_TOKEN: ${{ github.token }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$modelArchive = Join-Path $env:RUNNER_TEMP 'models.7z'
gh release download v1.7.9 --repo hgmzhn/manga-translator-ui --pattern models.7z --output $modelArchive --clobber
if ($LASTEXITCODE -ne 0) { throw 'Failed to download model files.' }
Remove-Item (Join-Path $env:PORTABLE_DIR 'models') -Recurse -Force -ErrorAction SilentlyContinue
7z x $modelArchive -o$env:PORTABLE_DIR -y
if ($LASTEXITCODE -ne 0) { throw 'Failed to extract model files.' }
Remove-Item $modelArchive -Force
if (-not (Test-Path (Join-Path $env:PORTABLE_DIR 'models'))) { throw 'The model archive did not contain a models directory.' }
- name: Smoke test bundled runtime
if: ${{ matrix.variant != 'amd' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
& $python -c "import PyQt6, torch, onnxruntime; print(torch.__version__, onnxruntime.__version__)"
if ($LASTEXITCODE -ne 0) { throw 'Bundled runtime import smoke test failed.' }
- name: Smoke test AMD bundled runtime metadata
if: ${{ matrix.variant == 'amd' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$python = Join-Path $env:PORTABLE_DIR 'packaging\python\python.exe'
& $python -c "from importlib.metadata import version; import PyQt6, onnxruntime; v = version('torch'); assert 'rocm7.2.1' in v, v; print(v, onnxruntime.__version__)"
if ($LASTEXITCODE -ne 0) { throw 'AMD bundled runtime metadata smoke test failed.' }
- name: Archive ${{ matrix.variant }} portable package
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$releaseDir = Join-Path $env:GITHUB_WORKSPACE 'release'
New-Item -ItemType Directory -Path $releaseDir -Force | Out-Null
Push-Location $env:PORTABLE_DIR
7z a -v1990m -m0=lzma2 -ms=on (Join-Path $releaseDir 'manga-translator-${{ matrix.variant }}-${{ github.ref_name }}.7z') .
$archiveExitCode = $LASTEXITCODE
Pop-Location
if ($archiveExitCode -ne 0) { throw 'Failed to archive the portable package.' }
- name: Upload ${{ matrix.variant }} portable archive
uses: actions/upload-artifact@v4
with:
name: manga-translator-${{ matrix.variant }}-portable
path: release/*.7z.*
release-and-publish:
needs: [build-portable]
runs-on: ubuntu-latest
name: Create Release and Publish Updates
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download portable archives
uses: actions/download-artifact@v4
with:
path: release_assets
merge-multiple: true
- name: Read CHANGELOG
id: changelog
run: |
set -Eeuo pipefail
VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
CHANGELOG_FILE="doc/CHANGELOG_v${VERSION}.md"
if [ -f "$CHANGELOG_FILE" ]; then
DELIM="END_OF_CHANGELOG_${GITHUB_RUN_ID}_${RANDOM}"
{
printf 'changelog<<%s\n' "$DELIM"
sed -e '$a\' "$CHANGELOG_FILE"
printf '%s\n' "$DELIM"
} >> "$GITHUB_OUTPUT"
else
printf 'changelog=%s\n' "未找到更新日志文件" >> "$GITHUB_OUTPUT"
fi
- name: Delete existing GitHub Release for this version
env:
GH_TOKEN: ${{ github.token }}
run: |
if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]]; then
echo "Not a semantic version tag: $GITHUB_REF_NAME; skip old release deletion."
exit 0
fi
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Deleting existing release $GITHUB_REF_NAME before publishing..."
gh release delete "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --yes
else
echo "No existing release for $GITHUB_REF_NAME."
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.changelog }}
files: release_assets/*
draft: false
prerelease: false