diff --git a/.github/workflows/README.md b/.github/workflows/README.md index fc62f73c..643cb9a5 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,108 +1,128 @@ -# GitHub Actions CI/CD 配置说明 +# CI/CD 工作流说明 -## 概述 +## 文件总览 -为VirtualPaper项目配置了完整的CI/CD流程,包含两个工作流文件: +| 文件 | 触发时机 | 职责 | +|---|---|---| +| `pre-publish-branch-ci-check.yml` | push / PR → dev · release · bugfix | 构建 + 单元测试 | +| `branch-protection.yml` | PR → main | 校验源分支合法性 | +| `auto-version-release.yml` | PR 合并到 main | 版本递增 · 打包 · 冒烟测试 · 发布 | -- **dev-ci.yml**: 当代码推送到dev分支时自动运行4个测试项目,并创建状态检查 -- **branch-protection.yml**: 确保只有dev分支可以合并到main分支 +--- -## 工作流程 +## 整体流程 -### 1. Dev分支持续集成 (dev-ci.yml) +``` + feature/* ──► dev ──┐ + │ CI 通过后创建 PR → main + release ──────────── ┤ + │ + bugfix ──────────── ┘ + │ + ▼ + main ──► 自动构建打包 ──► 草稿 Release + │ + 人工验证后发布 +``` -**触发条件:** -- 推送到dev分支 -- 创建针对dev分支的PR +--- -**执行步骤:** -1. 设置.NET环境(8.0.x) -2. 恢复项目依赖 -3. 构建解决方案 -4. 按顺序运行4个测试项目: - - VirtualPaper.Core.Test - - VirtualPaper.UI.Test - - VirtualPaper.ML.Test - - VirtualPaper.Shader.Test -5. 发布测试结果报告 -6. 创建commit状态检查(context: `ci/dev-tests`) +## 工作流一:Pre-publish CI -### 2. 分支保护检查 (branch-protection.yml) +> **文件:** `pre-publish-branch-ci-check.yml` +> **触发:** push 或 PR 到 `dev` / `release` / `bugfix` -**触发条件:** -- 创建针对main分支的PR -- 更新针对main分支的PR +对预发布分支进行持续集成,确保代码在合并到 main 之前质量达标。 -**检查逻辑:** -1. 验证源分支是否为dev -2. 如果不是dev分支,创建失败状态并阻止合并 -3. 在PR中添加友好的提示评论 +**执行内容:** +1. 构建整个解决方案(Release 配置) +2. 并行运行 4 项单元测试:Core · UI · ML · Shader +3. 汇总测试结果,写入 commit status `ci-check/pre-publish-tests` -## 分支保护规则设置 +该 status 是合并到 main 的**必需检查项**之一。 -为确保只有测试通过的代码才能合并到main分支,需要在GitHub仓库中设置分支保护规则: +--- -### 设置步骤: +## 工作流二:分支保护 -1. 进入GitHub仓库 → Settings → Branches -2. 为main分支添加或编辑保护规则: +> **文件:** `branch-protection.yml` +> **触发:** 创建 / 更新 PR → `main` -``` -分支名称模式: main +只允许 `dev`、`release`、`bugfix` 三个分支向 main 发起 PR,其他分支一律拦截。 -☑️ Require a pull request before merging - ☑️ Require approvals: 1 - ☑️ Dismiss stale reviews when new commits are pushed +| 源分支 | 版本变化 | 说明 | +|---|---|---| +| `release` | Minor +1 | 0.4.x.x → 0.5.0.0 | +| `dev` | Build +1 | 0.5.2.x → 0.5.3.0 | +| `bugfix` | Revision +1 | 0.5.3.0 → 0.5.3.1 | -☑️ Require status checks to pass before merging - ☑️ Require branches to be up to date before merging - 添加必需的状态检查: - - ci/dev-tests - - branch-protection/source-validation +--- -☑️ Include administrators -``` +## 工作流三:自动发布 -## 工作原理 +> **文件:** `auto-version-release.yml` +> **触发:** PR 成功合并到 `main` -1. **开发阶段**: - - Feature分支合并到dev时,自动运行测试 - - 只有dev分支可以创建到main的PR -2. **发布阶段**: - - 创建dev→main的PR时,检查两个状态: - - `ci/dev-tests`: dev分支最新commit的测试结果 - - `branch-protection/source-validation`: 源分支验证结果 -3. **合并控制**: - - 只有两个状态都为success时,PR才能被合并 +### 执行流程 +``` +prepare + │ 计算新版本号,记录各分支 SHA,捕获 PR 标题和正文 + ▼ +build + │ MSBuild Release 构建,产物上传为 Artifact + ▼ +package + │ InnoSetup 编译安装包,上传为 Artifact(保留 30 天) + ▼ +smoke_test ← 失败则终止并回滚 + │ 静默安装 → 启动主程序 → 等待 UI 被拉起(最多 20s) + │ 若未自动拉起:兜底手动启动 UI + │ 再次尝试启动 UI → 断言只有 1 个 UI 进程(单例验证) + ▼ +bump + │ 更新 csproj / InnoSetup / README badge 版本号并提交到 main + ▼ +sync + │ 将版本文件同步到 release / dev / bugfix 分支 + ▼ +summary + │ 打印构建摘要 + ▼ +release(草稿) + 创建 Draft GitHub Release + - Tag 和标题:v{版本号} + - 正文:本次 PR 的描述 + - 附件:安装包 exe + - 状态:草稿,不公开,等待人工验证后发布 +``` -## 使用流程 +> 任意步骤失败时,`rollback` job 自动触发,对 main 及三个预发布分支执行 `git revert`。 -### 日常开发: -1. Feature分支 → dev分支(触发测试) -2. 查看dev分支commit旁的测试状态 +--- -### 发布流程: -1. 创建dev → main的PR -2. 系统自动检查: - - 源分支是否为dev(`branch-protection/source-validation`) - - dev分支最新commit的测试结果(`ci/dev-tests`) -3. 两个检查都通过时才能合并 +## 发版操作步骤 -## 错误处理 +``` +1. 在 dev / release / bugfix 分支完成开发 +2. 确认 CI 全绿(ci-check/pre-publish-tests) +3. 创建 PR → main,等待审核通过并合并 +4. CI 自动完成:构建 → 打包 → 冒烟测试 → 提交版本号 → 创建草稿 Release +5. 在 GitHub Releases 页面下载草稿附件,本地安装验证 +6. 确认无误后点击 Edit → Publish release 正式发布 +``` -### 如果从非dev分支创建PR到main: -- ❌ 工作流会失败并创建失败状态 -- 📝 PR中会显示友好的错误说明 -- 🔒 PR会被阻止合并 +--- -### 如果dev分支测试失败: -- ❌ `ci/dev-tests`状态为failure -- 🔒 PR会被阻止合并 -- 🛠️ 需要修复测试问题后重新推送到dev +## 分支保护规则(需手动配置) -## 注意事项 +> Settings → Branches → Add branch protection rule → `main` -- 确保dev分支有最新的测试结果 -- 分支保护规则中需要添加两个状态检查 -- 只有dev分支可以合并到main分支 \ No newline at end of file +``` +☑ Require a pull request before merging +☑ Require status checks to pass before merging + 必需检查: + - ci-check/pre-publish-tests + - Check Source Branch +☑ Include administrators +``` diff --git a/.github/workflows/auto-version-release.yml b/.github/workflows/auto-version-release.yml index 4955bfa5..edf6870d 100644 --- a/.github/workflows/auto-version-release.yml +++ b/.github/workflows/auto-version-release.yml @@ -1,5 +1,5 @@ -name: Auto Version & Build on Main Merge -run-name: "[Auto] Pre-publish | ${{ github.event.pull_request.head.ref }} -> main | ${{ github.event.pull_request.title }}" +name: Auto Sync and Publish +run-name: "[Auto] Publish | ${{ github.event.pull_request.head.ref }} -> main | ${{ github.event.pull_request.title }}" on: pull_request: @@ -26,15 +26,18 @@ jobs: runs-on: windows-latest if: github.event.pull_request.merged == true outputs: - source_branch: ${{ steps.branch_info.outputs.source_branch }} - increment_type: ${{ steps.branch_info.outputs.increment_type }} + source_branch: ${{ steps.branch_info.outputs.source_branch }} + increment_type: ${{ steps.branch_info.outputs.increment_type }} current_version: ${{ steps.version.outputs.current_version }} - new_version: ${{ steps.version.outputs.new_version }} + new_version: ${{ steps.version.outputs.new_version }} new_version_text: ${{ steps.version.outputs.new_version_text }} - main_sha: ${{ steps.shas.outputs.main_sha }} - dev_sha: ${{ steps.shas.outputs.dev_sha }} - bugfix_sha: ${{ steps.shas.outputs.bugfix_sha }} - release_sha: ${{ steps.shas.outputs.release_sha }} + current_version_text: ${{ steps.version.outputs.current_version_text }} + main_sha: ${{ steps.shas.outputs.main_sha }} + dev_sha: ${{ steps.shas.outputs.dev_sha }} + bugfix_sha: ${{ steps.shas.outputs.bugfix_sha }} + release_sha: ${{ steps.shas.outputs.release_sha }} + pr_title: ${{ steps.pr_info.outputs.pr_title }} + pr_body: ${{ steps.pr_info.outputs.pr_body }} steps: - name: Checkout with full history @@ -69,7 +72,7 @@ jobs: env: PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | - $sourceBranch = $env:PR_HEAD_REF + $sourceBranch = $env:PR_HEAD_REF $incrementType = "patch" if ($sourceBranch -eq "release") { @@ -85,15 +88,15 @@ jobs: Write-Host "Unknown source branch: $sourceBranch - defaulting to patch" } - Write-Output "source_branch=$sourceBranch" >> $env:GITHUB_OUTPUT - Write-Output "increment_type=$incrementType" >> $env:GITHUB_OUTPUT + Write-Output "source_branch=$sourceBranch" >> $env:GITHUB_OUTPUT + Write-Output "increment_type=$incrementType" >> $env:GITHUB_OUTPUT - name: Calculate new version id: version shell: powershell run: | - $csprojPath = "src/VirtualPaper/VirtualPaper.csproj" - $content = Get-Content $csprojPath -Raw + $csprojPath = "src/VirtualPaper/VirtualPaper.csproj" + $content = Get-Content $csprojPath -Raw $versionMatch = [regex]::Match($content, '(\d+)\.(\d+)\.(\d+)\.(\d+)') if (-not $versionMatch.Success) { @@ -101,33 +104,56 @@ jobs: exit 1 } - $major = [int]$versionMatch.Groups[1].Value - $minor = [int]$versionMatch.Groups[2].Value - $build = [int]$versionMatch.Groups[3].Value + $major = [int]$versionMatch.Groups[1].Value + $minor = [int]$versionMatch.Groups[2].Value + $build = [int]$versionMatch.Groups[3].Value $revision = [int]$versionMatch.Groups[4].Value $currentVersion = "$major.$minor.$build.$revision" $incrementType = "${{ steps.branch_info.outputs.increment_type }}" if ($incrementType -eq "feature") { - $minor = $minor + 1 # 第2位 +1 - $build = 0 # 第3位 清零 - $revision = 0 # 第4位 清零 + $minor = $minor + 1 # 第2位 +1 + $build = 0 # 第3位 清零 + $revision = 0 # 第4位 清零 } elseif ($incrementType -eq "minor") { - $build = $build + 1 # 第3位 +1 - $revision = 0 # 第4位 清零 + $build = $build + 1 # 第3位 +1 + $revision = 0 # 第4位 清零 } else { - $revision = $revision + 1 # 第4位 +1 + $revision = $revision + 1 # 第4位 +1 } - $newVersion = "$major.$minor.$build.$revision" + $newVersion = "$major.$minor.$build.$revision" $newVersionText = "$major$minor$build$revision" Write-Host "Version: $currentVersion -> $newVersion" - Write-Output "current_version=$currentVersion" >> $env:GITHUB_OUTPUT - Write-Output "new_version=$newVersion" >> $env:GITHUB_OUTPUT - Write-Output "new_version_text=$newVersionText" >> $env:GITHUB_OUTPUT + Write-Output "current_version=$currentVersion" >> $env:GITHUB_OUTPUT + Write-Output "new_version=$newVersion" >> $env:GITHUB_OUTPUT + Write-Output "new_version_text=$newVersionText" >> $env:GITHUB_OUTPUT + + - name: Capture PR title and body + id: pr_info + shell: powershell + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + # PowerShell 5.1 的 >> 和 Out-File -Encoding utf8 均会写入 UTF-16 LE 或带 BOM 的 UTF-8 + # $GITHUB_OUTPUT 必须是无 BOM 的 UTF-8,使用 .NET API 显式指定 + $utf8NoBom = [System.Text.UTF8Encoding]::new($false) + + # PR title(单行) + [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "pr_title=$env:PR_TITLE`n", $utf8NoBom) + Write-Host "PR title: $env:PR_TITLE" + + # PR body(多行,heredoc delimiter 格式,完整保留换行和特殊字符) + $delimiter = [System.Guid]::NewGuid().ToString("N") + $body = $env:PR_BODY -replace "`r`n", "`n" # 统一为 LF,避免 CRLF 写入 GITHUB_OUTPUT + [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "pr_body<<$delimiter`n", $utf8NoBom) + [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "$body`n", $utf8NoBom) + [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "$delimiter`n", $utf8NoBom) + Write-Host "PR body captured ($($body.Length) chars)" # ───────────────────────────────────────────────────────────── # Job 2: 编译 .NET Release @@ -159,17 +185,17 @@ jobs: shell: powershell run: | $csprojPath = "src/VirtualPaper/VirtualPaper.csproj" - $content = Get-Content $csprojPath -Raw + $content = Get-Content $csprojPath -Raw $newVersion = "${{ needs.prepare.outputs.new_version }}" $newContent = $content -replace '\d+\.\d+\.\d+\.\d+', "$newVersion" Set-Content $csprojPath -Value $newContent -NoNewline Write-Host "Patched AssemblyVersion to $newVersion (build-time only)" - - name: Restore dependencies - run: dotnet restore src/VirtualPaper.sln + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 - - name: Build Release - run: dotnet build src/VirtualPaper.sln --configuration Release --no-restore + - name: Rebuild solution + run: msbuild src/VirtualPaper.sln /t:Rebuild /p:Configuration=Release /p:Platform="Any CPU" /restore /m - name: Upload build output uses: actions/upload-artifact@v4 @@ -202,35 +228,50 @@ jobs: path: src/ - name: Install Inno Setup 6.7.1 - uses: Minionguyjpro/Inno-Setup-Action@v1.2.4 - with: - version: 6.7.1 - options: /VERYSILENT + shell: powershell + run: choco install innosetup --version=6.7.1 --no-progress -y - name: Patch version in InnoSetup script (local only, not committed) shell: powershell run: | - $setupPath = "InnoSetup/setup.iss" - $content = Get-Content $setupPath -Raw - $newVersion = "${{ needs.prepare.outputs.new_version }}" + $setupPath = "InnoSetup/setup.iss" + $content = Get-Content $setupPath -Raw + $newVersion = "${{ needs.prepare.outputs.new_version }}" $newVersionText = "${{ needs.prepare.outputs.new_version_text }}" - $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`"" + $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`"" $newContent = $newContent -replace '#define MyAppVersionText "[\d]+"', "#define MyAppVersionText `"$newVersionText`"" Set-Content $setupPath -Value $newContent -NoNewline Write-Host "Patched InnoSetup version to $newVersion (build-time only)" + - name: Download InnoDependencyInstaller + shell: powershell + run: | + $url = "https://raw.githubusercontent.com/DomGries/InnoDependencyInstaller/master/CodeDependencies.iss" + $dest = "InnoSetup\CodeDependencies.iss" + Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing + Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " CodeDependencies.iss downloaded" + - name: Compile installer id: build_installer - uses: Minionguyjpro/Inno-Setup-Action@v1.2.4 - with: - path: InnoSetup/setup.iss - options: /O+ + shell: powershell + run: | + $iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" + if (-not (Test-Path $iscc)) { + Write-Error "ISCC.exe not found at: $iscc" + exit 1 + } + & $iscc "InnoSetup\setup.iss" /O+ + if ($LASTEXITCODE -ne 0) { + Write-Error "Inno Setup compilation failed (exit code $LASTEXITCODE)" + exit 1 + } + Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Installer compiled successfully" - name: Record installer info id: installer_info shell: powershell run: | - $outputDir = "InnoSetup\Output" + $outputDir = "InnoSetup\Output" $installerFiles = Get-ChildItem -Path $outputDir -Filter "*.exe" -ErrorAction SilentlyContinue if ($installerFiles.Count -eq 0) { Write-Error "No installer found in output directory!" @@ -241,33 +282,230 @@ jobs: Write-Output "installer_path=$installerPath" >> $env:GITHUB_OUTPUT Write-Output "installer_name=$($installerFiles[0].Name)" >> $env:GITHUB_OUTPUT - - name: Upload installer as artifact + - name: Generate SHA256 for installer + shell: powershell + run: | + $hash = (Get-FileHash -Path "${{ steps.installer_info.outputs.installer_path }}" -Algorithm SHA256).Hash.ToLower() + Set-Content -Path "${{ github.workspace }}\InnoSetup\Output\SHA256.txt" -Value $hash + + - name: Upload installer and SHA256 as artifact uses: actions/upload-artifact@v4 with: - name: VirtualPaper-installer-v${{ needs.prepare.outputs.new_version }} - path: ${{ steps.installer_info.outputs.installer_path }} + name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }} + path: | + ${{ steps.installer_info.outputs.installer_path }} + InnoSetup/Output/SHA256.txt retention-days: 30 # ───────────────────────────────────────────────────────────── - # Job 4: 提交版本号变更到 main(安装包制作成功后才提交) + # Job 4: 安装包冒烟测试(静默安装 + 启动验证) + # 阶段 1 - 等待 VirtualPaper.exe 自动拉起 UI(最多 10s) + # 阶段 2 - 兜底:若未自动拉起则手动启动 UI + # 阶段 3 - 再次尝试启动 UI,断言全局只有 1 个 UI 进程(单例守卫) + # 阶段 4 - 验证 UI 无法在主进程未启动的情况下独立运行(应弹出错误 MessageBox 并阻塞) # ───────────────────────────────────────────────────────────── - publish: - name: Commit Version to Main + smoke_test: + name: Smoke Test Installer runs-on: windows-latest needs: [prepare, package] + steps: + - name: Setup .NET (app runtime dependency) + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Download installer artifact + uses: actions/download-artifact@v4 + with: + name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }} + path: dist/ + + - name: Silent install + id: install + shell: powershell + run: | + $installer = Get-ChildItem "dist\" -Filter "*.exe" | Select-Object -First 1 + if (-not $installer) { + Write-Error "No installer found in dist\" + exit 1 + } + $installDir = "C:\VirtualPaperSmokeTest" + Write-Host "Installer : $($installer.FullName)" + Write-Host "Target dir: $installDir" + $proc = Start-Process -FilePath $installer.FullName ` + -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /DIR=`"$installDir`"" ` + -Wait -PassThru + if ($proc.ExitCode -ne 0) { + Write-Error "Installer failed with exit code $($proc.ExitCode)" + exit 1 + } + Write-Output "install_dir=$installDir" >> $env:GITHUB_OUTPUT + Write-Host "[OK] Installation completed" + + - name: Verify key files exist + shell: powershell + run: | + $base = "${{ steps.install.outputs.install_dir }}" + $required = @( + "$base\VirtualPaper.exe", + "$base\Plugins\UI\VirtualPaper.UI.exe" + ) + $failed = $false + foreach ($f in $required) { + if (Test-Path $f) { + Write-Host "[OK] $f" + } else { + Write-Host "[MISSING] $f" -ForegroundColor Red + $failed = $true + } + } + if ($failed) { + Write-Error "Required files missing after installation" + exit 1 + } + + - name: Launch and verify UI process + shell: powershell + run: | + $installDir = "${{ steps.install.outputs.install_dir }}" + $mainExe = "$installDir\VirtualPaper.exe" + $uiExe = "$installDir\Plugins\UI\VirtualPaper.UI.exe" + + # ── 阶段 1:启动主程序,等待其自动拉起 UI(最多 10s)────────────── + Write-Host "Launching: $mainExe" + $main = Start-Process -FilePath $mainExe -PassThru -ErrorAction Stop + + $uiProc = $null + $autoWait = 10 + for ($i = 1; $i -le $autoWait; $i++) { + Start-Sleep -Seconds 1 + $main.Refresh() + if ($main.HasExited) { + Write-Error "VirtualPaper.exe exited unexpectedly after ${i}s (exit code: $($main.ExitCode))" + exit 1 + } + $uiProc = Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($uiProc) { + Write-Host "[OK] VirtualPaper.UI auto-started after ${i}s (PID: $($uiProc.Id))" + break + } + Write-Host " [wait] ${i}s - waiting for VirtualPaper.UI to be auto-spawned..." + } + + # ── 阶段 2:兜底 - 若未自动拉起则手动启动 UI ────────────────────── + if (-not $uiProc) { + Write-Host "[FALLBACK] UI not auto-spawned after ${autoWait}s, launching manually: $uiExe" + Start-Process -FilePath $uiExe -ErrorAction Stop + Start-Sleep -Seconds 5 + $uiProc = Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $uiProc) { + Write-Error "VirtualPaper.UI.exe failed to start even when launched manually" + taskkill /F /T /PID $main.Id 2>$null + exit 1 + } + Write-Host "[OK] VirtualPaper.UI started via fallback (PID: $($uiProc.Id))" + } + + # ── 阶段 3:单例守卫验证 - 再次启动 UI,期望仍只有 1 个进程 ──────── + Write-Host "Verifying singleton: launching VirtualPaper.UI again..." + Start-Process -FilePath $uiExe -ErrorAction SilentlyContinue + Start-Sleep -Seconds 3 + + $uiProcs = @(Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue) + if ($uiProcs.Count -ne 1) { + Write-Error "Singleton check FAILED: expected 1 VirtualPaper.UI process, found $($uiProcs.Count)" + taskkill /F /T /PID $main.Id 2>$null + Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + exit 1 + } + Write-Host "[OK] Singleton check passed: only 1 VirtualPaper.UI process (PID: $($uiProcs[0].Id))" + + # ── 清理 ─────────────────────────────────────────────────────────── + taskkill /F /T /PID $main.Id 2>$null + Get-Process -Name "VirtualPaper.UI" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + Write-Host "[OK] Smoke test passed" + + - name: Verify UI cannot start without main process + shell: powershell + run: | + $installDir = "${{ steps.install.outputs.install_dir }}" + $uiExe = "$installDir\Plugins\UI\VirtualPaper.UI.exe" + + # 前置清理:确保主进程与 UI 进程均未运行(强杀残留) + @("VirtualPaper", "VirtualPaper.UI") | ForEach-Object { + $procs = Get-Process -Name $_ -ErrorAction SilentlyContinue + if ($procs) { + Write-Host "[CLEANUP] Killing residual process: $_" + $procs | Stop-Process -Force -ErrorAction SilentlyContinue + } + } + Start-Sleep -Seconds 1 + + # ── 直接启动 UI(不启动主进程)────────────────────────────────────── + Write-Host "Launching VirtualPaper.UI.exe without main process..." + $uiProc = Start-Process -FilePath $uiExe -PassThru -ErrorAction Stop + + # 等待足够时间让 MessageBox 弹出 + Start-Sleep -Seconds 3 + + # ── 断言:进程应仍在运行(被 MessageBox 阻塞,未正常退出)──────────── + $uiProc.Refresh() + if ($uiProc.HasExited) { + Write-Error "VirtualPaper.UI exited on its own - expected it to be blocked by a MessageBox" + exit 1 + } + + # ── 进一步断言:确认是 MessageBox(#32770)而非 UI 正常启动 ────────── + # 通过 UIAutomation 枚举该进程的顶层窗口并读取 Win32 ClassName + # MessageBox 的 ClassName 固定为 #32770,无需注入 C# 代码 + Add-Type -AssemblyName UIAutomationClient + Add-Type -AssemblyName UIAutomationTypes + + $desktop = [System.Windows.Automation.AutomationElement]::RootElement + $pidCond = New-Object System.Windows.Automation.PropertyCondition( + [System.Windows.Automation.AutomationElement]::ProcessIdProperty, + $uiProc.Id) + $wins = $desktop.FindAll([System.Windows.Automation.TreeScope]::Children, $pidCond) + $classes = @($wins | ForEach-Object { $_.Current.ClassName }) + Write-Host " Visible window classes: $($classes -join ', ')" + + if (-not ($classes -contains '#32770')) { + Write-Error "Expected a MessageBox (#32770) but none found - UI may have started normally, guard logic broken" + Stop-Process -Id $uiProc.Id -Force -ErrorAction SilentlyContinue + exit 1 + } + Write-Host "[OK] VirtualPaper.UI is blocked by a MessageBox (#32770) as expected (PID: $($uiProc.Id))" + + # ── 清理:强杀被阻塞的 UI 进程 ────────────────────────────────────── + Stop-Process -Id $uiProc.Id -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + $uiProc.Refresh() + if (-not $uiProc.HasExited) { + taskkill /F /PID $uiProc.Id 2>$null + } + Write-Host "[OK] Standalone-UI guard test passed" + + # ───────────────────────────────────────────────────────────── + # Job 5: 将版本号递增变更提交到 main(冒烟测试通过后才提交) + # ───────────────────────────────────────────────────────────── + bump: + name: Bump Version to main + runs-on: windows-latest + needs: [prepare, package, smoke_test] + steps: - name: Checkout main with full history uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.RELEASE_PAT }} fetch-depth: 0 - name: Update version in csproj shell: powershell run: | $csprojPath = "src/VirtualPaper/VirtualPaper.csproj" - $content = Get-Content $csprojPath -Raw + $content = Get-Content $csprojPath -Raw $newVersion = "${{ needs.prepare.outputs.new_version }}" $newContent = $content -replace '\d+\.\d+\.\d+\.\d+', "$newVersion" Set-Content $csprojPath -Value $newContent -NoNewline @@ -276,11 +514,11 @@ jobs: - name: Update version in InnoSetup script shell: powershell run: | - $setupPath = "InnoSetup/setup.iss" - $content = Get-Content $setupPath -Raw - $newVersion = "${{ needs.prepare.outputs.new_version }}" + $setupPath = "InnoSetup/setup.iss" + $content = Get-Content $setupPath -Raw + $newVersion = "${{ needs.prepare.outputs.new_version }}" $newVersionText = "${{ needs.prepare.outputs.new_version_text }}" - $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`"" + $newContent = $content -replace '#define MyAppVersion "[\d\.]+"', "#define MyAppVersion `"$newVersion`"" $newContent = $newContent -replace '#define MyAppVersionText "[\d]+"', "#define MyAppVersionText `"$newVersionText`"" Set-Content $setupPath -Value $newContent -NoNewline Write-Host "Updated InnoSetup version to: $newVersion" @@ -289,7 +527,7 @@ jobs: shell: powershell run: | $readmePath = "README.md" - $content = Get-Content $readmePath -Raw + $content = Get-Content $readmePath -Raw $newVersion = "${{ needs.prepare.outputs.new_version }}" $newContent = $content -replace '(img\.shields\.io/badge/release-v)[\d\.]+(-blue)', "`${1}$newVersion`$2" Set-Content $readmePath -Value $newContent -NoNewline @@ -306,57 +544,70 @@ jobs: Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version ${{ needs.prepare.outputs.new_version }} committed to main" # ───────────────────────────────────────────────────────────── - # Job 5: 将版本号同步回源分支(dev / bugfix) + # Job 6: 将版本号同步到 pre-publish branches # ───────────────────────────────────────────────────────────── sync: - name: Sync Version to Source Branch + name: Sync Version to Pre-publish Branches runs-on: windows-latest - needs: [prepare, publish] + needs: [prepare, bump] steps: - name: Checkout with full history uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.RELEASE_PAT }} fetch-depth: 0 - - name: Sync to source branch + - name: Sync version files to pre-publish branches shell: powershell run: | - $sourceBranch = "${{ needs.prepare.outputs.source_branch }}" + $newVersion = "${{ needs.prepare.outputs.new_version }}" - if ($sourceBranch -eq "dev" -or $sourceBranch -eq "bugfix" -or $sourceBranch -eq "release") { - Write-Host "Syncing version back to $sourceBranch..." - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" - # 必须先 fetch,确保 origin/main 拿到 publish job 刚推送的最新 commit - $ErrorActionPreference = 'Continue' - git fetch origin main 2>&1 | Out-Null - $ErrorActionPreference = 'Stop' + # 确保拿到 bump job 刚推送的最新 main + $ErrorActionPreference = 'Continue' + git fetch origin 2>&1 | Out-Null + $ErrorActionPreference = 'Stop' - git checkout -B $sourceBranch origin/$sourceBranch - # 用 origin/main 而不是 local main,避免本地引用未更新导致分支分叉 - git merge origin/main --ff-only - git push origin $sourceBranch - Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version synced to $sourceBranch" - } else { - Write-Host "Unknown source branch: $sourceBranch, skipping sync" + foreach ($branch in @("release", "dev", "bugfix")) { + Write-Host "" + Write-Host "--- Syncing version to $branch ---" + + git checkout -B $branch origin/$branch + + # 直接从 origin/main 取版本相关文件,无需 merge,不产生冲突 + git checkout origin/main -- src/VirtualPaper/VirtualPaper.csproj InnoSetup/setup.iss README.md + Write-Host " [OK] version files pulled from origin/main" + + git add src/VirtualPaper/VirtualPaper.csproj InnoSetup/setup.iss README.md + + # 若无变更则跳过 commit(分支本来就在最新版本时) + $status = git status --porcelain + if ($status) { + git commit -m "[Github CI] chore: sync version $newVersion from main [skip ci]" + git push origin $branch + Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " Version $newVersion synced to $branch" + } else { + Write-Host "[SKIP] $branch already at version $newVersion" + } } # ───────────────────────────────────────────────────────────── - # Job 6: 汇总输出(始终执行) + # Job 7: 汇总输出(始终执行) # ───────────────────────────────────────────────────────────── summary: name: Summary runs-on: ubuntu-latest - needs: [prepare, build, package, publish, sync] + needs: [prepare, build, package, smoke_test, bump, sync] if: >- !cancelled() && needs.prepare.result == 'success' && needs.build.result == 'success' && needs.package.result == 'success' && - needs.publish.result == 'success' && + needs.smoke_test.result == 'success' && + needs.bump.result == 'success' && needs.sync.result == 'success' steps: @@ -376,24 +627,60 @@ jobs: echo -e "\033[0;36mPackage : ${{ needs.package.outputs.installer_name }}\033[0m" echo "" echo -e "\033[0;32m[OK]\033[0m Version updated and committed to main" - echo -e "\033[0;32m[OK]\033[0m Version synced to ${{ needs.prepare.outputs.source_branch }}" + echo -e "\033[0;32m[OK]\033[0m Version synced to pre-publish branches (release / dev / bugfix)" echo -e "\033[0;32m[OK]\033[0m Installer uploaded as Artifact" + echo -e "\033[0;32m[OK]\033[0m Smoke test passed (install + launch + singleton)" + echo -e "\033[0;32m[OK]\033[0m Draft GitHub Release will be created: v${{ needs.prepare.outputs.new_version }}" echo -e "\033[0;32m==========================================\033[0m" # ───────────────────────────────────────────────────────────── - # Job 7: 失败回滚(任意 job 失败时执行) + # Job 8: 创建草稿 GitHub Release(所有步骤均成功后执行) + # - 等待 summary 通过,即代表全流程无任何错误 + # - 以 PR body 作为 Release 正文 + # - Tag / Release 标题均为 "v版本号" + # - 将 Installer exe 作为 Release 附件上传 + # - 创建为草稿(draft),需人工验证后手动点击发布 + # ───────────────────────────────────────────────────────────── + release: + name: Create Draft GitHub Release + runs-on: ubuntu-latest + needs: [prepare, package, summary] + if: needs.summary.result == 'success' + + steps: + - name: Download installer artifact + uses: actions/download-artifact@v4 + with: + name: VirtualPaper-Installer-v${{ needs.prepare.outputs.new_version }} + path: dist/ + + - name: Create Draft Release & Tag + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.prepare.outputs.new_version }} + name: v${{ needs.prepare.outputs.new_version }} + body: ${{ needs.prepare.outputs.pr_body }} + files: | + dist/*.exe + dist/SHA256.txt + draft: true # 草稿状态,不对外公开,等待人工验证后发布 + make_latest: false # 草稿阶段不标记为最新,发布时再生效 + token: ${{ secrets.GITHUB_TOKEN }} + + # ───────────────────────────────────────────────────────────── + # Job 9: 失败回滚(任意 job 失败时执行) # ───────────────────────────────────────────────────────────── rollback: name: Rollback on Failure runs-on: windows-latest - needs: [prepare, build, package, publish, sync] + needs: [prepare, build, package, smoke_test, bump, sync] if: always() && failure() && needs.prepare.result == 'success' steps: - name: Checkout with full history uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.RELEASE_PAT }} fetch-depth: 0 - name: Rollback changes @@ -405,16 +692,14 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - # 拉取最新远端状态(用 Continue 避免进度信息触发 NativeCommandError) $ErrorActionPreference = 'Continue' git fetch origin 2>&1 | Out-Null $ErrorActionPreference = 'Stop' - $mainSha = "${{ needs.prepare.outputs.main_sha }}" - $devSha = "${{ needs.prepare.outputs.dev_sha }}" - $bugfixSha = "${{ needs.prepare.outputs.bugfix_sha }}" - $releaseSha = "${{ needs.prepare.outputs.release_sha }}" - $sourceBranch = "${{ needs.prepare.outputs.source_branch }}" + $mainSha = "${{ needs.prepare.outputs.main_sha }}" + $devSha = "${{ needs.prepare.outputs.dev_sha }}" + $bugfixSha = "${{ needs.prepare.outputs.bugfix_sha }}" + $releaseSha = "${{ needs.prepare.outputs.release_sha }}" # --- 回滚 main --- if ($mainSha) { @@ -422,57 +707,40 @@ jobs: $currentMain = git rev-parse origin/main 2>&1 $ErrorActionPreference = 'Stop' if ($LASTEXITCODE -eq 0 -and $currentMain -ne $mainSha) { - Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " main: $currentMain -> $mainSha" + Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " main: reverting version bump commit" git checkout -B main origin/main - git reset --hard $mainSha - # 追加空 commit,防止 force push 后再次触发本 workflow - git commit --allow-empty -m "[Github CI] rollback: reverted version bump [skip ci]" - git push --force origin main + git revert HEAD --no-commit + git commit -m "[Github CI] rollback: revert version bump [skip ci]" + git push origin main Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " main rolled back" } else { Write-Host "[SKIP] main: no changes detected, rollback not needed" } } - # --- 回滚源分支(dev / bugfix)--- - if ($sourceBranch -eq "dev" -and $devSha) { - $ErrorActionPreference = 'Continue' - $currentDev = git rev-parse origin/dev 2>&1 - $ErrorActionPreference = 'Stop' - if ($LASTEXITCODE -eq 0 -and $currentDev -ne $devSha) { - Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " dev: $currentDev -> $devSha" - git checkout -B dev origin/dev - git reset --hard $devSha - git push --force origin dev - Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " dev rolled back" - } else { - Write-Host "[SKIP] dev: no changes detected, rollback not needed" - } - } elseif ($sourceBranch -eq "bugfix" -and $bugfixSha) { - $ErrorActionPreference = 'Continue' - $currentBugfix = git rev-parse origin/bugfix 2>&1 - $ErrorActionPreference = 'Stop' - if ($LASTEXITCODE -eq 0 -and $currentBugfix -ne $bugfixSha) { - Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " bugfix: $currentBugfix -> $bugfixSha" - git checkout -B bugfix origin/bugfix - git reset --hard $bugfixSha - git push --force origin bugfix - Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " bugfix rolled back" - } else { - Write-Host "[SKIP] bugfix: no changes detected, rollback not needed" - } - } elseif ($sourceBranch -eq "release" -and $releaseSha) { + # --- 回滚 pre-publish branches (release / dev / bugfix) --- + $branchShaMap = @{ + "release" = $releaseSha + "dev" = $devSha + "bugfix" = $bugfixSha + } + + foreach ($b in $branchShaMap.Keys) { + $targetSha = $branchShaMap[$b] + if (-not $targetSha) { continue } + $ErrorActionPreference = 'Continue' - $currentRelease = git rev-parse origin/release 2>&1 + $currentSha = git rev-parse origin/$b 2>&1 $ErrorActionPreference = 'Stop' - if ($LASTEXITCODE -eq 0 -and $currentRelease -ne $releaseSha) { - Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " release: $currentRelease -> $releaseSha" - git checkout -B release origin/release - git reset --hard $releaseSha - git push --force origin release - Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " release rolled back" + if ($LASTEXITCODE -eq 0 -and $currentSha -ne $targetSha) { + Write-Host "[ROLLBACK]" -ForegroundColor Cyan -NoNewline; Write-Host " ${b}: reverting version sync commit" + git checkout -B $b origin/$b + git revert HEAD --no-commit + git commit -m "[Github CI] rollback: revert version sync [skip ci]" + git push origin $b + Write-Host "[OK]" -ForegroundColor Green -NoNewline; Write-Host " $b rolled back" } else { - Write-Host "[SKIP] release: no changes detected, rollback not needed" + Write-Host "[SKIP] ${b}: no changes detected, rollback not needed" } } diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml index 7cbeb592..29298aff 100644 --- a/.github/workflows/branch-protection.yml +++ b/.github/workflows/branch-protection.yml @@ -13,7 +13,6 @@ concurrency: permissions: contents: read - statuses: write jobs: check-source-branch: @@ -41,24 +40,3 @@ jobs: echo " bugfix -> main : Urgent bugfix (patch version +1)" exit 1 fi - - - name: Create status check - if: always() - uses: actions/github-script@v7 - with: - script: | - const sourceBranch = context.payload.pull_request.head.ref; - const allowed = ['release', 'dev', 'bugfix']; - const isAllowed = allowed.includes(sourceBranch); - - await github.rest.repos.createCommitStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: context.payload.pull_request.head.sha, - state: isAllowed ? 'success' : 'failure', - target_url: context.payload.repository.html_url + '/actions/runs/' + context.runId, - description: isAllowed - ? `[OK] Source branch validation passed: ${sourceBranch} -> main` - : `[FAILED] Only release, dev and bugfix branches allowed to merge to main`, - context: 'branch-protection/source-validation' - }); diff --git a/InnoSetup/CodeDependencies.iss b/InnoSetup/CodeDependencies.iss new file mode 100644 index 00000000..99f1419f --- /dev/null +++ b/InnoSetup/CodeDependencies.iss @@ -0,0 +1,853 @@ +[Code] +// https://github.com/DomGries/InnoDependencyInstaller + +// types and variables +type + TDependency_Entry = record + Filename: String; + Parameters: String; + Title: String; + URL: String; + Checksum: String; + ForceSuccess: Boolean; + RestartAfter: Boolean; + Components: String; + end; + +var + Dependency_List: array of TDependency_Entry; + Dependency_NeedToRestart, Dependency_ForceX86, Dependency_ForceX64: Boolean; + Dependency_Components: String; + Dependency_DownloadPage: TDownloadWizardPage; + +function Dependency_IsEntryActive(const Entry: TDependency_Entry): Boolean; +begin + Result := (Entry.Components = '') or WizardIsComponentSelected(Entry.Components); +end; + +procedure Dependency_Add(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, RestartAfter: Boolean); +var + Dependency: TDependency_Entry; + DependencyCount: Integer; +begin + Dependency.Filename := Filename; + Dependency.Parameters := Parameters; + Dependency.Title := Title; + + if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin + Dependency.URL := ''; + end else begin + Dependency.URL := URL; + end; + + Dependency.Checksum := Checksum; + Dependency.ForceSuccess := ForceSuccess; + Dependency.RestartAfter := RestartAfter; + Dependency.Components := Dependency_Components; + + DependencyCount := GetArrayLength(Dependency_List); + SetArrayLength(Dependency_List, DependencyCount + 1); + Dependency_List[DependencyCount] := Dependency; +end; + + +procedure Dependency_InitializeWizard; +begin + Dependency_DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil); +end; + + +function Dependency_PrepareToInstall(var NeedsRestart: Boolean): String; +var + DependencyCount, DependencyIndex, ResultCode: Integer; + Retry: Boolean; + TempValue: String; +begin + DependencyCount := GetArrayLength(Dependency_List); + + if DependencyCount > 0 then begin + Dependency_DownloadPage.Show; + + for DependencyIndex := 0 to DependencyCount - 1 do begin + if not Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin + continue; + end; + if Dependency_List[DependencyIndex].URL <> '' then begin + Dependency_DownloadPage.Clear; + Dependency_DownloadPage.Add(Dependency_List[DependencyIndex].URL, Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Checksum); + + Retry := True; + while Retry do begin + Retry := False; + + try + Dependency_DownloadPage.Download; + except + if Dependency_DownloadPage.AbortedByUser then begin + Result := Dependency_List[DependencyIndex].Title; + DependencyIndex := DependencyCount; + end else begin + case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of + IDABORT: begin + Result := Dependency_List[DependencyIndex].Title; + DependencyIndex := DependencyCount; + end; + IDRETRY: begin + Retry := True; + end; + end; + end; + end; + end; + end; + end; + + if Result = '' then begin + for DependencyIndex := 0 to DependencyCount - 1 do begin + if not Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin + continue; + end; + Dependency_DownloadPage.SetText(Dependency_List[DependencyIndex].Title, ''); + Dependency_DownloadPage.SetProgress(DependencyIndex + 1, DependencyCount + 1); + + while True do begin + ResultCode := 0; +#ifdef Dependency_CustomExecute + if {#Dependency_CustomExecute}(ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, ResultCode) then begin +#else + if ShellExec('', ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin +#endif + if Dependency_List[DependencyIndex].RestartAfter then begin + if DependencyIndex = DependencyCount - 1 then begin + Dependency_NeedToRestart := True; + end else begin + NeedsRestart := True; + Result := Dependency_List[DependencyIndex].Title; + end; + break; + end else if (ResultCode = 0) or Dependency_List[DependencyIndex].ForceSuccess then begin // ERROR_SUCCESS (0) + break; + end else if ResultCode = 1641 then begin // ERROR_SUCCESS_REBOOT_INITIATED (1641) + NeedsRestart := True; + Result := Dependency_List[DependencyIndex].Title; + break; + end else if ResultCode = 3010 then begin // ERROR_SUCCESS_REBOOT_REQUIRED (3010) + Dependency_NeedToRestart := True; + break; + end; + end; + + case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependency_List[DependencyIndex].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of + IDABORT: begin + Result := Dependency_List[DependencyIndex].Title; + break; + end; + IDIGNORE: begin + break; + end; + end; + end; + + if Result <> '' then begin + break; + end; + end; + + if NeedsRestart then begin + TempValue := '"' + ExpandConstant('{srcexe}') + '" /restart=1 /LANG="' + ExpandConstant('{language}') + '" /DIR="' + WizardDirValue + '" /GROUP="' + WizardGroupValue + '" /TYPE="' + WizardSetupType(False) + '" /COMPONENTS="' + WizardSelectedComponents(False) + '" /TASKS="' + WizardSelectedTasks(False) + '"'; + if WizardNoIcons then begin + TempValue := TempValue + ' /NOICONS'; + end; + RegWriteStringValue(HKA, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', '{#SetupSetting("AppName")}', TempValue); + end; + end; + + Dependency_DownloadPage.Hide; + end; +end; + +#ifndef Dependency_NoUpdateReadyMemo + +#endif +function Dependency_UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; +var + DependencyIndex: Integer; + DependencyMemo: String; +begin + Result := ''; + if MemoUserInfoInfo <> '' then begin + Result := Result + MemoUserInfoInfo + Newline + NewLine; + end; + if MemoDirInfo <> '' then begin + Result := Result + MemoDirInfo + Newline + NewLine; + end; + if MemoTypeInfo <> '' then begin + Result := Result + MemoTypeInfo + Newline + NewLine; + end; + if MemoComponentsInfo <> '' then begin + Result := Result + MemoComponentsInfo + Newline + NewLine; + end; + if MemoGroupInfo <> '' then begin + Result := Result + MemoGroupInfo + Newline + NewLine; + end; + if MemoTasksInfo <> '' then begin + Result := Result + MemoTasksInfo; + end; + + DependencyMemo := ''; + for DependencyIndex := 0 to GetArrayLength(Dependency_List) - 1 do begin + if Dependency_IsEntryActive(Dependency_List[DependencyIndex]) then begin + DependencyMemo := DependencyMemo + #13#10 + '%1' + Dependency_List[DependencyIndex].Title; + end; + end; + + if DependencyMemo <> '' then begin + if MemoTasksInfo = '' then begin + Result := Result + SetupMessage(msgReadyMemoTasks); + end; + Result := Result + FmtMessage(DependencyMemo, [Space]); + end; +end; + + +function Dependency_NeedRestart: Boolean; +begin + Result := Dependency_NeedToRestart; +end; + +function Dependency_IsArm64: Boolean; +begin + Result := not Dependency_ForceX86 and not Dependency_ForceX64 and IsArm64; +end; + +function Dependency_IsX64: Boolean; +begin + Result := not Dependency_ForceX86 and Is64BitInstallMode; +end; + +function Dependency_String(const x86, x64, arm64: String): String; +begin + if Dependency_IsArm64 then begin + Result := arm64; + end else if Dependency_IsX64 then begin + Result := x64; + end else begin + Result := x86; + end; +end; + +function Dependency_ArchSuffix: String; +begin + Result := Dependency_String('', '_x64', '_arm64'); +end; + +function Dependency_ArchTitle: String; +begin + Result := Dependency_String(' (x86)', ' (x64)', ' (arm64)'); +end; + +function Dependency_IsNetCoreInstalled(Runtime: String; Major, Minor, Revision: Word): Boolean; +var + Path: String; + ResultCode: Integer; + Output: TExecOutput; + LineIndex: Integer; + LineParts: TArrayOfString; + PackedVersion: Int64; + LineMajor, LineMinor, LineRevision, LineBuild: Word; +begin + if not RegQueryStringValue(HKLM32, 'SOFTWARE\dotnet\Setup\InstalledVersions\' + Dependency_String('x86', 'x64', 'arm64'), 'InstallLocation', Path) or not FileExists(Path + 'dotnet.exe') then begin + Path := ExpandConstant(Dependency_String('{commonpf32}', '{commonpf64}', '{commonpf64}')) + '\dotnet\'; + end; + if ExecAndCaptureOutput(Path + 'dotnet.exe', '--list-runtimes', '', SW_HIDE, ewWaitUntilTerminated, ResultCode, Output) and (ResultCode = 0) then begin + for LineIndex := 0 to Length(Output.StdOut) - 1 do begin + LineParts := StringSplit(Trim(Output.StdOut[LineIndex]), [' '], stExcludeEmpty); + + if (Length(LineParts) > 1) and (Lowercase(LineParts[0]) = Lowercase(Runtime)) and StrToVersion(LineParts[1], PackedVersion) then begin + UnpackVersionComponents(PackedVersion, LineMajor, LineMinor, LineRevision, LineBuild); + + if (LineMajor = Major) and (LineMinor = Minor) and (LineRevision >= Revision) then begin + Result := True; + exit; + end; + end; + end; + end; + Result := False; +end; + +procedure Dependency_AddDotNet35; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net35-sp1 + if not IsDotNetInstalled(net35, 1) then begin + Dependency_Add('dotnetfx35.exe', + '/lang:enu /passive /norestart', + '.NET Framework 3.5 Service Pack 1', + 'https://download.microsoft.com/download/2/0/E/20E90413-712F-438C-988E-FDAA79A8AC3D/dotnetfx35.exe', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet40; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net40 + if not IsDotNetInstalled(net4full, 0) then begin + Dependency_Add('dotNetFx40_Full_setup.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.0', + 'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet45; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net452 + if not IsDotNetInstalled(net452, 0) then begin + Dependency_Add('dotnetfx45.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.5.2', + 'https://go.microsoft.com/fwlink/?LinkId=397707', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet46; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net462 + if not IsDotNetInstalled(net462, 0) then begin + Dependency_Add('dotnetfx46.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.6.2', + 'https://go.microsoft.com/fwlink/?linkid=780596', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet47; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net472 + if not IsDotNetInstalled(net472, 0) then begin + Dependency_Add('dotnetfx47.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.7.2', + 'https://go.microsoft.com/fwlink/?LinkId=863262', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet48; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net48 + if not IsDotNetInstalled(net48, 0) then begin + Dependency_Add('dotnetfx48.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.8', + 'https://go.microsoft.com/fwlink/?LinkId=2085155', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet481; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net481 + if not IsDotNetInstalled(net481, 0) then begin + Dependency_Add('dotnetfx481.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.8.1', + 'https://go.microsoft.com/fwlink/?LinkId=2203304', + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 3, 1, 32) then begin + Dependency_Add('netcore31' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Core Runtime 3.1.32' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/de4b3438-24a2-4d1d-a845-97355cf97b71/515abb880478b49f7c1bced8fbf07b16/dotnet-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/476eba79-f17f-49c8-a213-0f24a22cd026/37c02de81ff5b76ac57a5427462395f1/dotnet-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/476eba79-f17f-49c8-a213-0f24a22cd026/37c02de81ff5b76ac57a5427462395f1/dotnet-runtime-3.1.32-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31Asp; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 3, 1, 32) then begin + Dependency_Add('netcore31asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 3.1.32' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/63b482d2-04b2-4dd4-baaf-d1e78de80738/40321091c872f4e77337b68fc61a5a07/aspnetcore-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/98910750-2644-472c-ab2b-17f315ccb953/c2a4c223ee11e2eec7d13744e7a45547/aspnetcore-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/98910750-2644-472c-ab2b-17f315ccb953/c2a4c223ee11e2eec7d13744e7a45547/aspnetcore-runtime-3.1.32-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 3, 1, 32) then begin + Dependency_Add('netcore31desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 3.1.32' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/3f353d2c-0431-48c5-bdf6-fbbe8f901bb5/542a4af07c1df5136a98a1c2df6f3d62/windowsdesktop-runtime-3.1.32-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/b92958c6-ae36-4efa-aafe-569fced953a5/1654639ef3b20eb576174c1cc200f33a/windowsdesktop-runtime-3.1.32-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/b92958c6-ae36-4efa-aafe-569fced953a5/1654639ef3b20eb576174c1cc200f33a/windowsdesktop-runtime-3.1.32-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 5, 0, 17) then begin + Dependency_Add('dotnet50' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 5.0.17' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/54683c13-6b04-4d7d-b4d4-1f055b50ea43/e99048e2840d57040e8312058853a5b9/dotnet-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/a0832b5a-6900-442b-af79-6ffddddd6ba4/e2df0b25dd851ee0b38a86947dd0e42e/dotnet-runtime-5.0.17-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/5.0.17/dotnet-runtime-5.0.17-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 5, 0, 17) then begin + Dependency_Add('dotnet50asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 5.0.17' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4bfa247d-321d-4b29-a34b-62320849059b/8df7a17d9aad4044efe9b5b1c423e82c/aspnetcore-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/3789ec90-2717-424f-8b9c-3adbbcea6c16/2085cc5ff077b8789ff938015392e406/aspnetcore-runtime-5.0.17-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/3789ec90-2717-424f-8b9c-3adbbcea6c16/2085cc5ff077b8789ff938015392e406/aspnetcore-runtime-5.0.17-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 5, 0, 17) then begin + Dependency_Add('dotnet50desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 5.0.17' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b6fe5f2a-95f4-46f1-9824-f5994f10bc69/db5ec9b47ec877b5276f83a185fdb6a0/windowsdesktop-runtime-5.0.17-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/3aa4e942-42cd-4bf5-afe7-fc23bd9c69c5/64da54c8864e473c19a7d3de15790418/windowsdesktop-runtime-5.0.17-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/5.0.17/windowsdesktop-runtime-5.0.17-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 6, 0, 36) then begin + Dependency_Add('dotnet60' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 6.0.36' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/727d79cb-6a4c-4a6b-bd9e-af99ad62de0b/5cd3550f1589a2f1b3a240c745dd1023/dotnet-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1a5fc50a-9222-4f33-8f73-3c78485a55c7/1cb55899b68fcb9d98d206ba56f28b66/dotnet-runtime-6.0.36-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/6.0.36/dotnet-runtime-6.0.36-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 6, 0, 36) then begin + Dependency_Add('dotnet60asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 6.0.36' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/8cfa7f46-88f2-4521-a2d8-59b827420344/447de18a48115ac0fe6f381f0528e7a5/aspnetcore-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/0f0ea01c-ef7c-4493-8960-d1e9269b718b/3f95c5bd383be65c2c3384e9fa984078/aspnetcore-runtime-6.0.36-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/0f0ea01c-ef7c-4493-8960-d1e9269b718b/3f95c5bd383be65c2c3384e9fa984078/aspnetcore-runtime-6.0.36-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 6, 0, 36) then begin + Dependency_Add('dotnet60desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 6.0.36' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/cdc314df-4a4c-4709-868d-b974f336f77f/acd5ab7637e456c8a3aa667661324f6d/windowsdesktop-runtime-6.0.36-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/f6b6c5dc-e02d-4738-9559-296e938dabcb/b66d365729359df8e8ea131197715076/windowsdesktop-runtime-6.0.36-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/6.0.36/windowsdesktop-runtime-6.0.36-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet70; +begin + // https://dotnet.microsoft.com/download/dotnet/7.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 7, 0, 20) then begin + Dependency_Add('dotnet70' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 7.0.20' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b2e820bd-b591-43df-ab10-1eeb7998cc18/661ca79db4934c6247f5c7a809a62238/dotnet-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/be7eaed0-4e32-472b-b53e-b08ac3433a22/fc99a5977c57cbfb93b4afb401953818/dotnet-runtime-7.0.20-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/7.0.20/dotnet-runtime-7.0.20-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet70Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/7.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 7, 0, 20) then begin + Dependency_Add('dotnet70asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 7.0.20' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/d84ac38e-a248-4c8d-b1fe-4ee092d6b4b1/9f0bf370619ab3da8869e467827a6dc6/aspnetcore-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10651a65-8afc-46e3-9287-fecb0e68504e/4c2bf0cdb44612f29d9b3f901098e13e/aspnetcore-runtime-7.0.20-win-x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/10651a65-8afc-46e3-9287-fecb0e68504e/4c2bf0cdb44612f29d9b3f901098e13e/aspnetcore-runtime-7.0.20-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet70Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/7.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 7, 0, 20) then begin + Dependency_Add('dotnet70desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 7.0.20' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b840017b-c69f-4724-a152-11020a0039e6/b74aa12e4ee765a3387a7dcd4ba56187/windowsdesktop-runtime-7.0.20-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/08bbfe8f-812d-479f-803b-23ea0bffce47/c320e4b037f3e92ab7ea92c3d7ea3ca1/windowsdesktop-runtime-7.0.20-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/7.0.20/windowsdesktop-runtime-7.0.20-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet80; +begin + // https://dotnet.microsoft.com/download/dotnet/8.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 8, 0, 27) then begin + Dependency_Add('dotnet80' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 8.0.27' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/8.0.27/dotnet-runtime-8.0.27-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet80Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/8.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 8, 0, 27) then begin + Dependency_Add('dotnet80asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 8.0.27' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.27/aspnetcore-runtime-8.0.27-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet80Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/8.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 8, 0, 27) then begin + Dependency_Add('dotnet80desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 8.0.27' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.27/windowsdesktop-runtime-8.0.27-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet90; +begin + // https://dotnet.microsoft.com/download/dotnet/9.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 9, 0, 16) then begin + Dependency_Add('dotnet90' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 9.0.16' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/9.0.16/dotnet-runtime-9.0.16-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet90Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/9.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 9, 0, 16) then begin + Dependency_Add('dotnet90asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 9.0.16' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/9.0.16/aspnetcore-runtime-9.0.16-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet90Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/9.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 9, 0, 16) then begin + Dependency_Add('dotnet90desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 9.0.16' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/9.0.16/windowsdesktop-runtime-9.0.16-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet100; +begin + // https://dotnet.microsoft.com/download/dotnet/10.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App', 10, 0, 8) then begin + Dependency_Add('dotnet100' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 10.0.8' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/Runtime/10.0.8/dotnet-runtime-10.0.8-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet100Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/10.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App', 10, 0, 8) then begin + Dependency_Add('dotnet100asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 10.0.8' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/10.0.8/aspnetcore-runtime-10.0.8-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet100Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/10.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App', 10, 0, 8) then begin + Dependency_Add('dotnet100desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 10.0.8' + Dependency_ArchTitle, + Dependency_String('https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-x86.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-x64.exe', 'https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/10.0.8/windowsdesktop-runtime-10.0.8-win-arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2005; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26347 + if not IsMsiProductInstalled(Dependency_String('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), PackVersionComponents(8, 0, 61000, 0)) then begin + Dependency_Add('vcredist2005' + Dependency_ArchSuffix + '.exe', + '/q', + 'Visual C++ 2005 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2008; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26368 + if not IsMsiProductInstalled(Dependency_String('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), PackVersionComponents(9, 0, 30729, 6161)) then begin + Dependency_Add('vcredist2008' + Dependency_ArchSuffix + '.exe', + '/q', + 'Visual C++ 2008 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2010; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26999 + if not IsMsiProductInstalled(Dependency_String('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), PackVersionComponents(10, 0, 40219, 0)) then begin + Dependency_Add('vcredist2010' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2010 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2012; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=30679 + if not IsMsiProductInstalled(Dependency_String('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), PackVersionComponents(11, 0, 61030, 0)) then begin + Dependency_Add('vcredist2012' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2012 Update 4 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2013; +begin + // https://support.microsoft.com/en-us/help/4032938 + if not IsMsiProductInstalled(Dependency_String('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), PackVersionComponents(12, 0, 40664, 0)) then begin + Dependency_Add('vcredist2013' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2013 Update 5 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2015To2022; +var + Version: String; + PackedVersion: Int64; +begin + // https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist + if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\' + Dependency_String('x86', 'x64', 'arm64'), 'Version', Version) and (Copy(Version, 1, 1) = 'v') then begin + Delete(Version, 1, 1); + end; + if not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 50, 35719, 0)) < 0) then begin + Dependency_Add('vcredist2022' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2015-2022 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://aka.ms/vc14/vc_redist.x86.exe', 'https://aka.ms/vc14/vc_redist.x64.exe', 'https://aka.ms/vc14/vc_redist.arm64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDirectX; +begin +#ifdef Dependency_Files_DirectX + ExtractTemporaryFile('dxwebsetup.exe'); +#endif + // https://www.microsoft.com/en-us/download/details.aspx?id=35 + Dependency_Add('dxwebsetup.exe', + '/q', + 'DirectX Runtime', + 'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe', + '', True, False); +end; + +procedure Dependency_AddSql2008Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=30438 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(10, 50, 4000, 0)) < 0) then begin + Dependency_Add('sql2008express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2008 R2 Service Pack 2 Express', + Dependency_String('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2012Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=56042 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(11, 0, 7001, 0)) < 0) then begin + Dependency_Add('sql2012express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2012 Service Pack 4 Express', + Dependency_String('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2014Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=57473 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(12, 0, 6024, 0)) < 0) then begin + Dependency_Add('sql2014express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2014 Service Pack 3 Express', + Dependency_String('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2016Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=103447 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(13, 0, 6404, 1)) < 0) then begin + Dependency_Add('sql2016express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2016 Service Pack 3 Express', + 'https://download.microsoft.com/download/f/a/8/fa83d147-63d1-449c-b22d-5fef9bd5bb46/SQLServer2016-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddSql2017Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=55994 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 0, 0, 0)) < 0) then begin + Dependency_Add('sql2017express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2017 Express', + 'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddSql2019Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=101064 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(15, 0, 0, 0)) < 0) then begin + Dependency_Add('sql2019express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2019 Express', + 'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddSql2022Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=104781 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(16, 0, 1000, 6)) < 0) then begin + Dependency_Add('sql2022express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2022 Express', + 'https://go.microsoft.com/fwlink/p/?linkid=2216019', + '', False, False); + end; +end; + +procedure Dependency_AddSql2025Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/sql-server/sql-server-downloads + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL17.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(17, 0, 1000, 7)) < 0) then begin + Dependency_Add('sql2025express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2025 Express', + 'https://download.microsoft.com/download/7ab8f535-7eb8-4b16-82eb-eca0fa2d38f3/SQL2025-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddWebView2; +begin + // https://developer.microsoft.com/en-us/microsoft-edge/webview2 + if not RegValueExists(HKLM, Dependency_String('SOFTWARE', 'SOFTWARE\WOW6432Node', 'SOFTWARE\WOW6432Node') + '\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv') then begin + Dependency_Add('MicrosoftEdgeWebview2Setup.exe', + '/silent /install', + 'WebView2 Runtime', + 'https://go.microsoft.com/fwlink/p/?LinkId=2124703', + '', False, False); + end; +end; + +procedure Dependency_AddAccessDatabaseEngine2016; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=54920 + if not RegKeyExists(HKLM, 'SOFTWARE\Microsoft\Office\16.0\Access Connectivity Engine\Engines\ACE') then begin + Dependency_Add('AccessDatabaseEngine2016' + Dependency_ArchSuffix + '.exe', + '/quiet', + 'Microsoft Access Database Engine 2016' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine.exe', 'https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe', 'https://download.microsoft.com/download/3/5/C/35C84C36-661A-44E6-9324-8786B8DBE231/accessdatabaseengine_X64.exe'), + '', False, False); + end; +end; + +[Files] +#ifdef Dependency_Path_DirectX +Source: "{#Dependency_Path_DirectX}dxwebsetup.exe"; Flags: dontcopy noencryption +#endif diff --git a/InnoSetup/setup.iss b/InnoSetup/setup.iss index 81eb054f..f70a992b 100644 --- a/InnoSetup/setup.iss +++ b/InnoSetup/setup.iss @@ -1,6 +1,8 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! +#include "CodeDependencies.iss" + #define AppId "{{95F4CECE-7C7C-40E1-B485-07DA64D1905F}" #define MyAppName "Virtual Paper" #define MyAppVersion "0.4.0.1" @@ -11,9 +13,9 @@ [CustomMessages] english.DeleteEverythigMsgBox=Do you want to delete data folder? -chinese.DeleteEverythigMsgBox=Ҫɾļ +chinese.DeleteEverythigMsgBox=您要删除数据文件夹吗? english.ClosingMsgBox=Application is running, do you want to close it? -chinese.ClosingMsgBox=ӦóУǷرգ +chinese.ClosingMsgBox=应用程序正在运行,是否关闭? [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. @@ -27,8 +29,12 @@ AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} AppCopyright=Virtual@PaperHammer +VersionInfoVersion={#MyAppVersion} +VersionInfoProductName={#MyAppName} +VersionInfoCompany={#MyAppPublisher} +VersionInfoDescription={#MyAppName} Installer DefaultDirName={autopf}\{#MyAppName} -ChangesAssociations=yes +ChangesAssociations=no DisableProgramGroupPage=yes OutputBaseFilename=virtualpaper_setup_x64_full_v{#MyAppVersionText} ; Uncomment the following line to run in non administrative install mode (install for current user only.) @@ -46,15 +52,14 @@ ArchitecturesInstallIn64BitMode=x64compatible UsePreviousAppDir=yes [Languages] -Name: "english"; MessagesFile: "compiler:Default.isl"; LicenseFile: "License\License.txt"; +Name: "english"; MessagesFile: "compiler:Default.isl"; LicenseFile: "License\LICENSE.txt"; Name: "chinese"; MessagesFile: "Languages\ChineseSimplified.isl"; [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked -; Name: "windowsstartup";Description: "{cm:AutoStartProgram,{#MyAppName}}" +; Name: "windowsstartup"; Description: "{cm:AutoStartProgram,{#MyAppName}}" [Files] -Source: "..\src\VirtualPaper\bin\Release\net8.0-windows10.0.19041.0\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "..\src\VirtualPaper\bin\Release\net8.0-windows10.0.19041.0\*"; DestDir: "{app}"; Excludes: "*.pdb,*.xml"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files @@ -67,40 +72,35 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall; Check: AutoLaunch -;skipifsilent [Code] -function GetPreviousInstallDir(): string; -var - path: string; +// InnoDependencyInstaller 初始化向导 UI +procedure InitializeWizard; begin - if RegQueryStringValue(HKLM, - 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1', - 'InstallLocation', path) then - begin - Result := path; - end - else if RegQueryStringValue(HKCU, - 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1', - 'InstallLocation', path) then - begin - Result := path; - end - else - Result := ''; + Dependency_InitializeWizard; end; -procedure InitializeWizard(); -var - oldDir: string; +// 在"准备安装"页面列出待安装的依赖项 +function UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; begin - oldDir := GetPreviousInstallDir(); + Result := Dependency_UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo); +end; - if oldDir <> '' then - begin - WizardForm.DirEdit.Text := oldDir; - end; +// 声明 .NET 8.0 Desktop Runtime 为安装前置依赖 +// InnoDependencyInstaller 自动处理检测、下载、安装 +// 静默安装时跳过(CI 环境已由 setup-dotnet action 提供运行时) +function PrepareToInstall(var NeedsRestart: Boolean): String; +begin + if not WizardSilent() then + Dependency_AddDotNet80Desktop; + Result := Dependency_PrepareToInstall(NeedsRestart); +end; + +// 安装完成后是否需要重启 +function NeedRestart: Boolean; +begin + Result := Dependency_NeedRestart; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); @@ -124,7 +124,7 @@ begin Result := True; if CheckForMutexes('Virtual:WALLPAPERSYSTEM') then begin - if SuppressibleMsgBox('{cm:ClosingMsgBox}', + if SuppressibleMsgBox(ExpandConstant('{cm:ClosingMsgBox}'), mbConfirmation, MB_OKCANCEL, IDOK) = IDOK then begin ShellExec('open', 'taskkill.exe', '/f /im {#MyAppExeName}', @@ -136,20 +136,6 @@ begin end; end; -function NetCoreNeedsInstall(version: String): Boolean; -var - netcoreRuntime: String; - resultCode: Integer; -begin - netcoreRuntime := 'Microsoft.WindowsDesktop.App'; - Result := not ( - Exec(ExpandConstant('{tmp}\netcorecheck.exe'), - netcoreRuntime + ' ' + version, - '', SW_HIDE, ewWaitUntilTerminated, resultCode) - and (resultCode = 0) - ); -end; - function CmdLineParamNotExists(const Value: string): Boolean; var I: Integer; @@ -165,5 +151,7 @@ end; function AutoLaunch(): Boolean; begin - Result := CmdLineParamNotExists('/NOAUTOLAUNCH'); + // /NOAUTOLAUNCH 显式禁止,或 /VERYSILENT(CI 静默安装)时不自动启动 + // /SILENT(业务自动更新)时仍正常重启 App + Result := CmdLineParamNotExists('/NOAUTOLAUNCH') and CmdLineParamNotExists('/VERYSILENT'); end; diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 00000000..aca8cfc6 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs b/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs index 94539c6a..563b8d26 100644 --- a/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs +++ b/src/VirtualPaper.Core.Test/T_AppUpdate/GithubUpdaterServiceTests.cs @@ -12,7 +12,7 @@ public class GithubUpdaterServiceTests { private static readonly Uri FakeUri = new("https://fake/setup.exe"); private static readonly Uri FakeShaUri = new("https://fake/SHA256.txt"); - private static readonly Version FakeVersion = new(1, 2, 3, 4); + private static readonly Version FakeVersion = new(0, 0, 0, 0); private const string FakeChangelog = "- bug fix"; [TestInitialize] diff --git a/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs b/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs index d69f086c..562aeb6e 100644 --- a/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs +++ b/src/VirtualPaper.DraftPanel/Views/WorkSpace.xaml.cs @@ -104,7 +104,7 @@ private void SyncWorkspaceUI() { // 根据当前的选中项,控制可见性 var selectedItem = TabViewControl.SelectedItem as ArcTabViewItem; foreach (var kvp in _tabToFrame) { - kvp.Value.Visibility = (kvp.Key == selectedItem) ? Visibility.Visible : Visibility.Collapsed; + kvp.Value.Visibility = (kvp.Key as ArcTabViewItem == selectedItem) ? Visibility.Visible : Visibility.Collapsed; } } #endregion diff --git a/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml b/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml index 79c2ee46..9defbb68 100644 --- a/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml +++ b/src/VirtualPaper.IntelligentPanel/Views/StyleTransferComp/StyleTransferIntroduction.xaml @@ -9,6 +9,29 @@ xmlns:markup="using:VirtualPaper.UIComponent.ArcMarkupExtension" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + + + + @@ -22,17 +45,20 @@ Width="480" Height="270" CornerRadius="12" - Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" VerticalAlignment="Center"> - - + + + + + - + - - - - + BeforeImageSourceKey="{x:Bind _intelli_Demo_SuperResolution_Origin}" + AfterImageSourceKey="{x:Bind _intelli_Demo_SuperResolution_Result}" + AutoAnimate="True" + AnimationDuration="0:0:2.5" + PauseDuration="0:0:1"/> enable true + + + + + diff --git a/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj b/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj index 50550ad0..764c66a8 100644 --- a/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj +++ b/src/VirtualPaper.Shader.Test/VirtualPaper.Shader.Test.csproj @@ -7,6 +7,13 @@ enable true + + + + + diff --git a/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj b/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj index e1db636c..2077bc10 100644 --- a/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj +++ b/src/VirtualPaper.UI.Test/VirtualPaper.UI.Test.csproj @@ -9,6 +9,19 @@ true + + + + + diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg new file mode 100644 index 00000000..2dd270fa Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/origin.jpg differ diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg new file mode 100644 index 00000000..7a229f68 Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/style_transfer/result.jpg differ diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg new file mode 100644 index 00000000..8fac6178 Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/origin.jpg differ diff --git a/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg new file mode 100644 index 00000000..7d4a26e8 Binary files /dev/null and b/src/VirtualPaper.UI/Assets/intelligent/demo/super_resolution/result.jpg differ diff --git a/src/VirtualPaper.UI/VirtualPaper.UI.csproj b/src/VirtualPaper.UI/VirtualPaper.UI.csproj index e8adcde4..ac485ff9 100644 --- a/src/VirtualPaper.UI/VirtualPaper.UI.csproj +++ b/src/VirtualPaper.UI/VirtualPaper.UI.csproj @@ -68,6 +68,10 @@ + + + + @@ -119,6 +123,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml b/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml index 3b7ead62..445e9abf 100644 --- a/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml +++ b/src/VirtualPaper.UIComponent/Resources/ResourceDictionary.xaml @@ -30,6 +30,11 @@ + + + + +