Files
ctbjrj/.agents/skills/winforms-designer-package/SKILL.md
T

331 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: winforms-designer-package
description: 把编译好的 .NET Framework 4.8 WinForms 项目(exe + 依赖 DLL + 图标)用 Inno Setup 6 打包成可分发的单文件安装包(setup.exe)。当用户说"打包""安装包""setup""发布""做成安装程序""Inno Setup""生成 setup.exe""打安装包""做成 exe 安装包""封装发布""更新包",或要求把 F:\pycode\wpf\src\WinFormsDesigner.Fx48 下 Samples\COOO\scr\<项目名> 里编译好的 exe 打包成安装包,或对"成套报价软件"项目发版打包(含版本号递增/Gitea Release/更新包的完整 8 步发版链,见文末专章)时,必须使用本技能。即使用户没说"Inno Setup",只要是要把编译产物做成可分发/可安装的形式就触发。涵盖:识别要打包的文件、写 setup.iss 脚本、中文语言包安装、编译安装包、图标嵌入、Program Files 权限处理、卸载清理等完整发布流程。
---
# WinForms 设计器项目打包器
把 winforms-designer-compile 技能编译出的 `<项目名>.exe` + 依赖 DLL + 图标,用 **Inno Setup 6** 打包成一个**单文件 setup.exe** 安装包,带中文安装向导、开始菜单/桌面快捷方式、控制面板卸载项。
> 本技能流程在 GCS 铜排壳体计算项目中**完整实战验证过**,每个步骤都真实跑通。
## ⛔ 铁律:不做测试安装,直接交付正式版(用户强制,2026-09-24)
**禁止 AI 自己做任何安装测试**——包括但不限于:
- 编译"测试变体"安装包(降权限 PrivilegesRequired=lowest / 改临时目录)再静默安装验证
- 把 setup.exe 装到本机临时目录、虚拟机去验证启动/卸载
- 安装后自动运行程序验证功能
**正确做法**:按需求直接编译**正式版** setup.exe(含正确的默认安装目录、权限、快捷方式),**立即交付给用户**,由**用户本人**安装和测试。测试中发现的任何问题由用户反馈后在下轮修复。
**原因**(xuanxing 项目实测教训):AI 侧测试变体要在用户桌面弹 UAC/错误框(如降权限后写公共桌面快捷方式报 `IPersistFile::Save 失败 0x80070005 拒绝访问`)、留残留目录和卸载表项,还要反复等待静默安装——比用户直接装一遍正式版**更慢更吵**。打包环节唯一要"验证"的只有 ISCC 编译输出 `Successful compile` 和产物存在。
## 前提:这是编译之后的环节
本技能接在 winforms-designer-compile 之后。进本技能前,`scr\<项目名>\` 目录里应该已经有:
- `<项目名>.exe`(csc 编译产物)
- 第三方 `.dll`(如 `AdvancedDataGridView.dll`)
- 可选 `app.ico`(程序图标)
如果还没有 exe,先去用 winforms-designer-compile 技能编译。
## 触发前提
- Inno Setup 6 已安装(默认在 `C:\Program Files (x86)\Inno Setup 6\`)。没装的话提示用户先装。
- 项目目录约定在 `F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\scr\<项目名>\`。
---
## 📋 完整打包流程(5 步)
### 第 1 步:识别需要打包的文件
进项目目录 `scr\<项目名>\`,按下表分类:
| 文件类型 | 来源 | 是否打包 |
|---------|------|---------|
| `<项目名>.exe` | csc 编译产物 | ✅ 必需 |
| 第三方 `.dll`(如 `AdvancedDataGridView.dll`) | 设计器/代码依赖 | ✅ 必需(缺了程序跑不起来) |
| `app.ico`(若有) | 自制图标 | ✅ 建议带(用于快捷方式) |
| `.cs` / `.ui.cs` / `.xml` 源码 | — | ❌ 不打包 |
| `build.bat` / `build.rsp` / `*.log` | — | ❌ 不打包 |
| `单价.cfg` / `*.cfg` 配置 | 运行时生成 | ⚠️ 一般不打包(程序用默认值重建) |
**判断要打包哪些 DLL**:读 `<窗体名>.dependencies.txt`(设计器生成的权威清单),或直接 `ls *.dll` 看项目里实际存在的 DLL。
### 第 2 步:建发布目录 + 复制运行时文件
在 `Samples\COOO\` 下建 `release\<项目名>\`,**只复制运行时必需文件**(源码和构建文件不进):
```bash
mkdir -p 'F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\release\<项目名>'
cp 'F:\...\scr\<项目名>\<项目名>.exe' 'F:\...\release\<项目名>\/'
cp 'F:\...\scr\<项目名>\AdvancedDataGridView.dll' 'F:\...\release\<项目名>\/' # 按实际依赖
cp 'F:\...\scr\<项目名>\app.ico' 'F:\...\release\<项目名>\/' # 若有图标
```
### 第 3 步:检查 Inno Setup 环境 + 中文语言包
**检查 Inno Setup**:
```bash
ls 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' 2>&1 && echo 'Inno Setup 已安装' || echo '未安装,需先装 Inno Setup 6'
```
**中文语言包**:Inno Setup 6 **默认不带中文简体语言包**。要做中文界面必须下载 `ChineseSimplified.isl` 放到 `C:\Program Files (x86)\Inno Setup 6\Languages\`(需管理员权限)。下载用 kira-96 社区维护版(已验证可用):
```bash
# 1. 下载到可写位置
curl -sL -o ChineseSimplified.isl 'https://raw.githubusercontent.com/kira-96/Inno-Setup-Chinese-Simplified-Translation/main/ChineseSimplified.isl'
# 2. 需管理员权限复制到 Program Files
powershell -NoProfile -Command "Start-Process cmd -ArgumentList '/c copy /Y ChineseSimplified.isl \"C:\Program Files (x86)\Inno Setup 6\Languages\\\"' -Verb RunAs -Wait"
# 3. 验证
ls 'C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl'
```
> ⚠️ **下载的语言包必须带 UTF-8 BOM**。下载后检查,没有 BOM 就补一个,否则中文界面会乱码:
> ```bash
> python -c "
> import os
> p=r'C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl'
> d=open(p,'rb').read()
> if d[:3]!=b'\xef\xbb\xbf': open(p,'wb').write(b'\xef\xbb\xbf'+d); print('已补 BOM')
> "
> ```
> (Program Files 需要管理员权限写入,可能要提权 PowerShell 跑。)
### 第 4 步:编写 setup.iss 脚本
在 `release\<项目名>\` 目录下建 `setup.iss`。下面是**实战验证过的完整模板**(GCS 铜排壳体计算项目),按需替换 `<...>` 占位:
```iss
; ============================================================
; <项目名> 安装包脚本 — Inno Setup 6
; 文件必须保存为 UTF-8 with BOM 编码(中文才能正确显示)
; ============================================================
[Setup]
AppName=<项目显示名>
AppVersion=1.0
AppPublisher=<作者或公司>
DefaultDirName=C:\<项目目录名>
DefaultGroupName=<项目显示名>
DisableProgramGroupPage=yes
OutputDir=Output
OutputBaseFilename=<项目名>_Setup
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
PrivilegesRequired=admin
; 安装包自身图标(用程序的 app.ico)
SetupIconFile=app.ico
UninstallDisplayIcon={app}\app.ico
UninstallDisplayName=<项目显示名>
[Languages]
Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
[Tasks]
Name: "desktopicon"; Description: "创建桌面快捷方式(&D)"; GroupDescription: "附加图标:"
[Files]
Source: "<项目名>.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "<第三方DLL>.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "app.ico"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
; 开始菜单快捷方式
Name: "{group}\<项目显示名>"; Filename: "{app}\<项目名>.exe"; IconFilename: "{app}\app.ico"; Comment: "<项目描述>"
Name: "{group}\卸载 <项目显示名>"; Filename: "{uninstallexe}"
; 桌面快捷方式
Name: "{commondesktop}\<项目显示名>"; Filename: "{app}\<项目名>.exe"; IconFilename: "{app}\app.ico"; Tasks: desktopicon
[Run]
; 安装完成可选启动
Filename: "{app}\<项目名>.exe"; Description: "立即启动 <项目显示名>"; Flags: nowait postinstall skipifsilent
[UninstallDelete]
; 卸载时彻底清理 {app} 目录
Type: filesandordirs; Name: "{app}"
```
#### setup.iss 段语法说明
| 段 | 作用 |
|----|------|
| `[Setup]` | 安装包基本信息、压缩、平台、权限。`DefaultDirName` = 默认安装位置(`{pf}` = Program Files,或直接写 `C:\xxx`)|
| `[Languages]` | 界面语言。中文用 `chinesesimp` + `ChineseSimplified.isl` |
| `[Tasks]` | 可勾选任务。桌面快捷方式任务默认勾选(**不要加 `Flags: checked`**,新版不认)|
| `[Files]` | 要打包的文件。`Flags: ignoreversion` = 不比对版本直接覆盖 |
| `[Icons]` | 快捷方式。`{group}` = 开始菜单文件夹,`{app}` = 安装目录,`{commondesktop}` = 公共桌面 |
| `[Run]` | 安装后动作。`postinstall` = 勾选"立即启动",`skipifsilent` = 静默安装时不启动 |
| `[UninstallDelete]` | 卸载时额外删除。`filesandordirs` 删整个目录及内容 |
#### ⚠️ setup.iss 的两个必做细节
**细节 1:必须 UTF-8 with BOM 编码**。中文(AppName、说明等)才能正确显示,否则装界面是乱码。写完 .iss 后检查并补 BOM:
```bash
python -c "
data = open('setup.iss','rb').read()
if data[:3] != b'\xef\xbb\xbf':
open('setup.iss','wb').write(b'\xef\xbb\xbf' + data)
print('已加 UTF-8 BOM')
"
```
**细节 2:`[Tasks]` 桌面快捷方式不要写 `Flags: checked`**。新版 Inno Setup 不认这个 flag,编译报 "Parameter Flags includes an unknown flag"。任务默认就是勾选的,直接省略 Flags。
### 第 5 步:编译安装包
```bash
cd 'F:\pycode\wpf\src\WinFormsDesigner.Fx48\bin\Debug\net48\Samples\COOO\release\<项目名>'
'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' setup.iss 2>&1 | tail -5
```
**成功标志**:末尾输出 `Successful compile`,并在 `Output\<项目名>_Setup.exe` 生成安装包(通常 1~3 MB,LZMA2 压缩后远小于源文件总和)。
> 编译时 `Warning: A message named "..." has not been defined for "chinesesimp"` 是**正常的**——中文语言包版本与 Inno Setup 主程序版本不完全同步,缺失的消息自动用英文 Default.isl 回退,不影响功能。
---
## 发布目录最终结构
```
Samples\COOO\release\<项目名>\
├── <项目名>.exe ← 编译产物
├── <第三方DLL>.dll ← 依赖
├── app.ico ← 图标
├── setup.iss ← Inno Setup 脚本(源文件,留着方便改版重打包)
└── Output\
└── <项目名>_Setup.exe ← 最终交付的安装包
```
**交付物就是 `Output\<项目名>_Setup.exe` 这一个文件**,用户双击即可安装,自带中文向导、快捷方式、卸载项。
---
## 图标制作(可选,让 exe 有专属图标)
如果想给程序做专属图标嵌入 exe,详见 `references/icon-generation.md`(Pillow 纯 Python 绘制多尺寸 ICO 的完整模板,零原生依赖)。
简要流程:
1. 用 Pillow 绘制 512×512 图标 → 存成多尺寸 `app.ico`
2. 编译时在 `build.rsp` 加 `/win32icon:app.ico`
3. 窗体构造函数加 `LoadAppIcon()`(`Icon.ExtractAssociatedIcon`),让窗口/任务栏也显示图标
> 图标嵌入 exe 是**编译环节**做的事(/win32icon 是 csc 参数),打包环节只是把已有的 app.ico 一起带进安装包用于快捷方式。两环节配合。
---
## 常见问题排查
| 现象 | 原因 | 解决 |
|------|------|------|
| 安装界面中文乱码 | setup.iss 不是 UTF-8 BOM 编码 | 按第 4 步"细节 1"补 BOM 后重新编译 |
| `Couldn't open include file "...ChineseSimplified.isl"` | 没装中文语言包 | 按第 3 步下载 ChineseSimplified.isl 到 Inno Setup 的 Languages 目录 |
| 中文语言包装了还乱码 | 语言包 .isl 没 UTF-8 BOM | 给 .isl 文件补 BOM(需管理员权限)|
| `Parameter "Flags" includes an unknown flag` | `[Tasks]` 写了 `Flags: checked` | 删掉 `Flags: checked`,任务默认就勾选 |
| 安装到 Program Files 后程序保存配置报错 | exe 目录无写入权限 | 程序里配置文件改存 `%APPDATA%\<项目名>\`(用 `Environment.SpecialFolder.ApplicationData`),并在 `[UninstallDelete]` 加 `{userappdata}\<项目名>` 卸载时清理 |
| 装的 exe 双击没反应 | 缺少第三方 DLL | 检查 `[Files]` 是否把所有依赖 DLL 都打包了 |
| 卸载后配置文件残留 | `[UninstallDelete]` 没覆盖配置目录 | 加 `Type: filesandordirs; Name: "{userappdata}\<项目名>"` |
| `error CS0016: 未能写入输出文件`(重新编译 exe 时) | exe 被安装包运行的进程占用 | 先关掉运行中的程序再重新编译 |
---
## 发布前的完整流程(汇总)
```
1. [编译] 改完 .cs → 用 winforms-designer-compile 编译出 exe
2. [识别] 读 dependencies.txt 确定依赖 DLL
3. [建目录] 建 release\<项目名>\,复制 exe + DLL + ico
4. [写脚本] 写 setup.iss(用模板,改占位)+ 确保有 UTF-8 BOM
5. [语言包] 确认装了中文语言包(没有就下载,记得带 BOM)
6. [编译包] ISCC.exe setup.iss → 生成 Output\<项目名>_Setup.exe
7. [交付] 不做任何测试安装(见顶部铁律)——把 Output\<项目名>_Setup.exe 直接交给用户,由用户自行安装测试
```
---
## 🏭 项目专章:成套报价软件 完整发版体系(2026-10-01 定稿,用户强制)
> 仓库根:`C:\Users\099978\Documents\SharpDevelop Projects\成套报价软件`。
> 该项目发版**不走上面的通用 5 步**,走下面的 **8 步固定链**(多了版本号递增、更新包、git、Gitea Release 环节)。
> 用户说"打包/发版/上传到 git"针对本项目时,必须按本节执行,顺序不可换。
```
① 递增版本号 → ② 清进程+编译 → ③ make_package 组装 → ④ ISCC 安装包
→ ⑤ make_update_package 更新包 → ⑥ git 提交推送 → ⑦ Gitea Release → ⑧ 验收
```
### ① 递增版本号(版本号铁律:每次发版必须递增)
```powershell
powershell -File tools\递增版本号.ps1 # 修订号+1;大版本: tools\递增版本号.ps1 1.1.0
```
唯一事实源 = `成套报价软件\Properties\AssemblyInfo.cs` 的 AssemblyVersion(脚本同步 setup.iss 的 AppVersion 与安装包文件名)。关于软件页运行时自动读程序集版本。输出必须显示两处 `[OK]`。
### ② 清进程 + 编译
```powershell
Get-Process | Where-Object { $_.Path -like '*成套报价软件*' -or $_.Path -like '*SharpDevelop Projects*' } | Stop-Process -Force
cd 成套报价软件 # ★ rsp 相对路径基于此子目录,在仓库根跑必报 CS2001
& "C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe" "@..\tools\verify_build.rsp" /warn:1
```
ExitCode=0 且无 warning 才继续(rsp 默认 /warn:0 压警告,排查必须 /warn:1)。
### ③ 组装发布目录
```powershell
python tools\make_package.py # 仓库根跑
```
核对输出:`DLL 复制完成,缺失: 无`;cadbt 段只有 node+CAD-Viewer(独立扒图 exe 已于 2026-10-01 退役);无 `!!` 警告。
### ④ ISCC 安装包
```powershell
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "release\成套报价软件\setup.iss"
```
lzma2 约 2 分钟;产物 `release\成套报价软件\Output\成套报价软件_Setup_<版本>.exe`(约 120MB);`Successful compile` 才算成。中文语言包已装(chinesesimp 消息缺失 Warning 属正常)。
### ⑤ 更新包
先改 `tools\make_update_package.py` 两处:`PKG` 目录名带版本(如 `更新包_1.0.4`)+ `说明` 本期文案(只写用户可感知变化;使用方法写清覆盖哪个文件、怎么验证版本)。再跑 `python tools\make_update_package.py`。当前只含 成套报价软件.exe+使用说明(<1MB)。
### ⑥ git 提交推送
`git add -A` → `git commit -m "发版 v<版本>:..."` → `git push`(remote 已内嵌令牌)。
### ⑦ Gitea Release
tag=`v<版本>`(与 AssemblyInfo 一致)。PowerShell API 模式(令牌与仓库地址见 sd-enhanced-components 技能第 8 条或项目记忆):
- 建 Release:`Invoke-RestMethod -Method Post -Headers @{Authorization="token <令牌>"} -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($body))`——**中文 body 必须 UTF8.GetBytes 否则乱码**
- 传附件:`System.Net.Http.HttpClient` + `MultipartFormDataContent`(字段名 `attachment`,PS5.1 无 -Form)
- Release 说明与更新包说明同源
### ⑧ 验收
附件大小合理(Setup ~120MB、更新包 <1MB);提醒用户装机验证:**关于软件页显示 v<版本>**、CAD/PDF 扒图窗口可用。
### 故障速查(本项目专属)
| 现象 | 处置 |
|------|------|
| 组装/替换文件报"正在使用" | ② 的进程没杀干净(查 node/AI 引擎/主程序) |
| 编译报 CS2001 全部源文件找不到 | 工作目录错了——必须 `成套报价软件\` 子目录 |
| ISCC 报文件找不到 | make_package 没跑或发布目录被清,回到 ③ |
| Release 附件上传 401 | 令牌失效——请用户在 Gitea 设置→应用重新生成(git remote 和 API 同步换) |
| push 被拒 fetch first | 用户网页传过文件——`git pull --rebase` 再 push,绝不 force push |
---
## 配套技能
- **winforms-designer-compile**:本技能的上游。先用它把 .cs + .ui.cs 编译成 exe,再交给本技能打包。
- **winforms-designer-xml**:生成/校验窗体 XML 布局,是整个流水线(XML → 编译 → 打包)的起点。