Files
update-client/SIMCAE打包上传.md
T

622 lines
30 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.
# SIMCAE 打包上传
本文站在 SIMCAE 开发者和发布人员的角度,说明拿到 Hub 更新客户端 SDK 后,SIMCAE 怎么打安装器、怎么生成 Qt IFW 交付包、怎么上传到 SimCAE Hub。
服务端部署流程见 simcae-hub 项目根目录《服务端部署.md》。普通发布人员只需要拿到已经打好的更新客户端 SDK;如果需要重新生成 SDK,见源代码仓库 `SIMCAE/update-client/updater打包成SDK.md`
本文下面的命令默认在 SIMCAE 项目根目录执行。下面用 SIMCAE 当前放在 simcae-hub 项目里的情况举例:
```powershell
cd .\SIMCAE
```
进入后再使用相对路径,例如 `.\installer``.\update-client``.\out\build\...`。这样不要求开发者的 SIMCAE 一定放在某个固定磁盘目录。
## 一、先理解交付物
客户端交付会涉及三类文件:
| 交付物 | 给谁用 | 作用 |
| --- | --- | --- |
| Hub 更新客户端 SDK | SIMCAE 开发者 | 提供 `Launcher.exe``Updater.exe``Bootstrap.exe` 和必要运行库 |
| Qt IFW 交付包 ZIP | 上传到 SimCAE Hub | 包含 IFW package 和 repository,服务端会校验、注入配置、发布仓库并重新生成客户安装器 |
| 客户安装器 | 最终客户 | 客户从门户下载后双击安装,安装后得到 `maintenancetool.exe` |
正式主线是:开发者只上传一个 Qt IFW 交付包 ZIP,客户只从门户下载客户安装器。客户不需要手动改服务器地址、token 或 `app_config.json`
## 二、准备 Hub 更新客户端 SDK
开发者应拿到 `UpdateClientSDK-With-QtDll.zip`
这个 ZIP 由 `update-client` 仓库的 SDK 打包脚本生成,SDK 维护者按源代码仓库 `SIMCAE/update-client/updater打包成SDK.md` 操作即可。
建议手动解压到 SIMCAE 仓库内的固定相对目录:`.\update-client\dist\UpdateClientSDK-With-QtDll`
这里的 `With-QtDll` 表示包里带的是运行所需的 Qt DLL,不是完整 Qt SDK。
如果公司内部统一把 SDK 放在别的位置,也可以,只要后面 `$UpdateClientSdk` 指向解压后的 SDK 目录即可。
解压后至少应有:
- `bin\Launcher.exe`
- `bin\Updater.exe`
- `bin\Bootstrap.exe`
SDK 包不应该包含最终客户配置,例如 `app_config.json``server_config.json``server_config.qrc``manifest_public_key.pem`。这些最终配置由服务端在上传发布包时生成或注入。
## 三、准备 SIMCAE 已编译产物
默认 SIMCAE 已经在开发机上完成 Release 编译。客户端打包文档不要求每次重新全量编译 SIMCAE,因为 SIMCAE 工程很大,打安装包时通常只需要复用已有 Release 产物。
需要确认:
| 内容 | 说明 |
| --- | --- |
| SIMCAE Release 构建目录 | 已经存在 `SimCAE.exe`、库文件、资源文件 |
| Qt Installer Framework | 已经安装 `binarycreator.exe``repogen.exe` |
| DAP 运行时 | 如果启用 DAP 组件,`DAPrailCalxml` 等运行时已放在打包规则要求的位置 |
| Hub 更新客户端 SDK | 已解压,并能找到 `Launcher.exe``Updater.exe``Bootstrap.exe` |
如果业务主程序启用了“必须从 Launcher 启动”的门禁,SIMCAE 编译时使用的 launch token 必须和服务端 `.env` 里的 `SIMCAE_LAUNCH_TOKEN` 一致。
## 四、设置本次打包版本
SIMCAE 安装器文件名、IFW 组件 `package.xml` 版本、repository 里的 `Updates.xml` 版本都来自 CMake 变量 `SimCAE_Version`。这个版本默认读取 SIMCAE 仓库最近的纯数字 Git tag,例如 `1.1.3`
先把几个容易混淆的“版本标签”分清楚:
| 名称 | 写在哪里 | 谁会读取 | 作用 |
| --- | --- | --- | --- |
| Git tag | SIMCAE 仓库提交,例如 `git tag 1.1.3` | CMake 版本脚本 | 生成 `SimCAE_Version`,再写入安装器文件名和组件元数据 |
| 组件版本 | `packages/<组件ID>/meta/package.xml``<Version>` | `repogen.exe` | 生成 repository 时写入 `Updates.xml` |
| repository 组件版本 | `Updates.xml` 里的 `<PackageUpdate><Name>...``<Version>...` | `maintenancetool.exe` | 客户端判断某个组件是否需要更新 |
| ZIP 文件名 | 例如 `SimCAE-Delivery-1.1.3-windows_x86_64-msvc.zip` | 人和管理后台记录 | 方便识别上传文件,不是 MaintenanceTool 的更新依据 |
所以 `git tag 1.1.3` 打的是 SIMCAE 源码提交标签,不是给 ZIP 文件打标签。它会被 CMake 读取后间接变成组件 `package.xml` 里的版本。真正决定 MaintenanceTool 是否更新的是服务器 repository 的 `Updates.xml`,而 `Updates.xml` 又来自组件自己的 `package.xml`
正式发布时推荐在 SIMCAE 仓库给本次发布提交打纯数字 tag,再打包:
```powershell
git tag 1.1.3
```
如果只是本机演示,不想改 SIMCAE 仓库 tag,可以临时指定本次打包版本。下面命令会创建一个本地临时脚本,让 CMake 本次配置时读到 `1.1.3`
```powershell
$Version = "1.1.3"
$GitVersionShim = "..\.tmp\git-version-$Version.cmd"
New-Item -ItemType Directory -Force (Split-Path $GitVersionShim) | Out-Null
@"
@echo off
if /I "%1"=="describe" (
echo $Version
exit /b 0
)
git %*
"@ | Set-Content -LiteralPath $GitVersionShim -Encoding ASCII
```
后续所有命令都复用这个 `$Version`。不要只改 ZIP 文件名,否则会出现文件名是 `1.1.3`,但组件 `package.xml``Updates.xml` 里版本还是 `0.10.1` 的错包;这种包上传后,MaintenanceTool 仍然会按 `0.10.1` 判断。
## 五、刷新现有 CMake 打包配置
下面命令只刷新已有构建目录的 CMake 配置,用来告诉打包目标 Qt IFW 在哪里,以及 Hub 更新客户端的本地编译产物和 SDK 兜底在哪里,不是全量重新编译 SIMCAE。
先确认当前 PowerShell 已经在 SIMCAE 项目根目录。下面给出一个常见 Qt IFW 安装路径示例;如果你的 Qt IFW 装在别的位置,只改 `$QtIfwRoot` 这一行。
```powershell
$Build = ".\out\build\SimCAE-release-vs2022-qt515-ifw"
$QtIfwRoot = "C:\Qt\Tools\QtInstallerFramework\4.11"
$HubUpdateClientRuntime = ".\update-client\out\bin\Release"
$UpdateClientSdk = ".\update-client\dist\UpdateClientSDK-With-QtDll"
```
先检查 SIMCAE 工程里的 Hub 更新客户端本地编译产物:
```powershell
Test-Path "$QtIfwRoot\bin\binarycreator.exe"
Test-Path "$QtIfwRoot\bin\repogen.exe"
Test-Path "$HubUpdateClientRuntime\Launcher.exe"
Test-Path "$HubUpdateClientRuntime\Updater.exe"
Test-Path "$HubUpdateClientRuntime\Bootstrap.exe"
```
如果上面三个 EXE 都存在,打 SIMCAE 安装包时会优先使用它们,不会再从 SDK 目录重复拿一份。
如果本地编译产物不存在,再检查 SDK 兜底目录:
```powershell
Test-Path "$UpdateClientSdk\bin\Launcher.exe"
Test-Path "$UpdateClientSdk\bin\Updater.exe"
Test-Path "$UpdateClientSdk\bin\Bootstrap.exe"
```
如果本次要让 `SimCAE.exe` 只能从 `Launcher.exe` 启动,先准备 SIMCAE 编译期使用的本地打包配置。这里的 `launch_token` 必须和服务端 `.env` 里的 `SIMCAE_LAUNCH_TOKEN` 完全一致。
```powershell
$LauncherProductConfig = "..\.tmp\simcae-launcher-product-config.json"
@'
{
"app_id": "simcae",
"product_code": "simcae",
"app_name": "SimCAE",
"launch_token": "SimCAE_Launch_Token_2026_ChangeMe_32Bytes",
"license_key": "SIMCAE_LOCAL_PACKAGING_LICENSE_2026"
}
'@ | Set-Content -LiteralPath $LauncherProductConfig -Encoding UTF8
```
刷新配置:
```powershell
cmake -S . -B $Build `
"-DSimCAE_QtIfwRoot=$QtIfwRoot" `
"-DSimCAE_PackageHubUpdateClient=ON" `
"-DSimCAE_HubUpdateClientRuntimeDir=$HubUpdateClientRuntime" `
"-DSimCAE_HubUpdateClientSdkDir=$UpdateClientSdk" `
"-DSimCAE_UseLauncher=ON" `
"-DSimCAE_LauncherProductConfigFile=$LauncherProductConfig" `
"-DGIT_EXECUTABLE=$GitVersionShim"
```
`SimCAE_PackageHubUpdateClient=ON` 只负责把 `Launcher.exe``Updater.exe``Bootstrap.exe` 放进 SIMCAE 安装包。正式客户配置文件,例如 `config/app_config.json``config/manifest_public_key.pem`,仍然由服务端在上传发布包时生成或注入,不从开发机本地目录带进最终客户包。
如果只想把 Hub 更新客户端打进安装包,但暂时不限制用户直接双击 `SimCAE.exe`,则把上面命令中的 `SimCAE_UseLauncher` 改为 `OFF`,并去掉 `SimCAE_LauncherProductConfigFile` 这一项。
## 六、生成客户安装器和 IFW package
执行打包目标:
```powershell
cmake --build $Build --config Release --target package_installer
```
这个目标会读取 `SIMCAE\installer` 下的配置和组件规则,整理 IFW package staging,并生成安装器。组件有哪些、每个组件包含哪些文件、组件是否必选、依赖哪些组件,应该由 SIMCAE 开发者在打包配置和 `package.xml.in` 里提前定义好;SimCAE Hub 不会自动猜测业务应该拆成哪些组件。
当前示例里已有两个 IFW 组件:
| 组件目录 | 组件含义 | 元数据来源 |
| --- | --- | --- |
| `packages/com.simcae.app` | 核心程序、Launcher、Updater、Bootstrap、核心库和通用资源 | `installer/packages/meta/package.xml.in` |
| `packages/com.simcae.dap` | DAP 求解器插件及运行资源 | `installer/packages/com.simcae.dap/meta/package.xml.in` |
Qt IFW 的组件 ID 来自 `packages/<组件ID>` 目录名,例如 `com.simcae.dap`。组件显示名称、版本、是否强制安装、依赖关系等来自该组件的 `meta/package.xml`,例如 `<DisplayName>``<Version>``<ForcedInstallation>``<Dependencies>`
常见输出:
| 输出 | 说明 |
| --- | --- |
| `$Build\package` | Qt IFW package staging 目录 |
| `$Build\SimCAE-<版本>-Windows-installer.exe` | 本地生成的客户安装器 |
确认核心组件里已经带上 Hub 更新客户端:
```powershell
Test-Path "$Build\package\packages\com.simcae.app\data\view\bin\Launcher.exe"
Test-Path "$Build\package\packages\com.simcae.app\data\view\bin\Updater.exe"
Test-Path "$Build\package\packages\com.simcae.app\data\view\bin\Bootstrap.exe"
Test-Path "$Build\package\packages\com.simcae.app\data\view\bin\SimCAE.exe"
```
预期都返回 `True`
再确认 package 里的组件版本就是本次 `$Version`
```powershell
$AppPackageXml = "$Build\package\packages\com.simcae.app\meta\package.xml"
$DapPackageXml = "$Build\package\packages\com.simcae.dap\meta\package.xml"
$AppVersion = ([xml](Get-Content -LiteralPath $AppPackageXml -Encoding UTF8 -Raw)).Package.Version
$DapVersion = ([xml](Get-Content -LiteralPath $DapPackageXml -Encoding UTF8 -Raw)).Package.Version
if ($AppVersion -ne $Version -or $DapVersion -ne $Version) {
throw "组件版本不一致:app=$AppVersion dap=$DapVersion expected=$Version"
}
Test-Path "$Build\SimCAE-$Version-Windows-installer.exe"
```
最后一行预期返回 `True`。如果这里不是 `True`,不要继续生成 repository。
## 七、生成 IFW repository
MaintenanceTool 读取的是 Qt IFW repository,不是客户安装器。
生成前先确认 `$Build\package` 已经存在,并且里面至少有 `config``packages`
```powershell
Test-Path "$Build\package\config\config.xml"
Test-Path "$Build\package\packages"
```
预期都返回 `True`。然后生成完整 repository
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\installer\scripts\build-ifw-repository.ps1" `
-PackageDir "$Build\package" `
-OutputDir "$Build\ifw-repository" `
-ZipFile "$Build\SimCAE-IFW-Repository-$Version-windows_x86_64-msvc.zip"
```
这条命令不是“给压缩包打版本标签”。它只是调用 Qt IFW 的 `repogen.exe`,从 `$Build\package\packages` 读取已经准备好的组件目录和 `meta/package.xml`,生成 `Updates.xml` 和组件 `.7z` 包,最后把 repository 目录压成 ZIP。ZIP 文件名里的 `$Version` 只是为了让发布人员识别文件。
生成后检查:
```powershell
Test-Path "$Build\ifw-repository\Updates.xml"
Select-String -LiteralPath "$Build\ifw-repository\Updates.xml" -Pattern "com.simcae.app|com.simcae.dap|<Version>"
```
生成的 repository 根目录必须包含 `Updates.xml`。这个 ZIP 一般不直接给客户,它是给 SimCAE Hub 后端托管,供 `maintenancetool.exe` 后续检查组件更新。
确认 repository 里的组件版本也是本次 `$Version`
```powershell
$UpdatesXml = "$Build\ifw-repository\Updates.xml"
$UpdatesContent = Get-Content -LiteralPath $UpdatesXml -Encoding UTF8 -Raw
if ($UpdatesContent -notmatch "<Version>$([regex]::Escape($Version))</Version>") {
throw "repository Updates.xml 中没有本次版本 $Version"
}
```
## 八、组装 Qt IFW 交付包 ZIP
推荐上传给 SimCAE Hub 的是交付包 ZIP,它把 package 和 repository 放在一起。客户安装器由服务端基于注入配置后的 package 重新生成。
目录结构建议:
- `package/config/`
- `package/packages/com.simcae.app/`
- `package/packages/com.simcae.dap/`
- `repository/Updates.xml`
- `repository/com.simcae.app/`
- `repository/com.simcae.dap/`
不要依赖交付包里的 `installer/SimCAE-<版本>-Windows-installer.exe` 作为最终客户安装器。本地生成的安装器没有服务端注入的 `app_config.json`,客户直接安装后 `Launcher.exe` 会缺少服务器地址、token 和主程序配置。服务端必须配置 `SIMCAE_IFW_BINARYCREATOR_PATH`Linux 服务端生成 Windows 安装器时还必须配置 `SIMCAE_IFW_INSTALLERBASE_WINDOWS_PATH` 指向 Windows 版 `installerbase.exe`,上传后由服务端重新生成客户门户可下载的安装器。
示例命令:
```powershell
$PlatformKey = "windows_x86_64-msvc"
$Bundle = "$Build\SimCAE-Delivery-$Version-$PlatformKey"
if (-not (Test-Path "$Build\ifw-repository\Updates.xml")) {
throw "缺少 repository/Updates.xml,请先生成 IFW repository"
}
Remove-Item -LiteralPath $Bundle -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force "$Bundle" | Out-Null
Copy-Item "$Build\package" "$Bundle\package" -Recurse -Force
Copy-Item "$Build\ifw-repository" "$Bundle\repository" -Recurse -Force
Compress-Archive -Path "$Bundle\*" -DestinationPath "$Build\SimCAE-Delivery-$Version-$PlatformKey.zip" -Force
```
`release.json` 不需要开发者手写。产品编码、产品名称、版本号、通道、平台、架构和 ABI 来自管理后台表单;运行目录和主程序名由服务端从核心组件中自动识别。
## 九、上传到管理后台
在浏览器打开管理后台,例如 `http://192.168.1.158:1798/login`
按左侧菜单顺序准备基础数据。第一次发布某个产品时要完整走一遍;后续同产品、同通道、同平台发布新版本时,只需要确认这些数据仍然存在:
1. 产品目录:确认产品编码,例如 `simcae`
2. 版本线:确认通道或版本线,例如 `stable`
3. 组件管理:确认核心组件和可选组件,例如 `com.simcae.app``com.simcae.dap`;如果用于 MaintenanceTool 更新,后台组件编码要和 IFW package 的 `packages/<组件ID>` 目录名一致。
4. 平台管理:确认 `windows``x86_64``msvc`
5. 产品版本:创建本次版本,例如 `1.1.3`
6. 软件发布:创建本次发布,关联产品版本和版本线。
7. 发布包:新增或编辑发布包。
上传完整 Qt IFW 交付包时,在“发布包”页面直接点击右上角新增发布包,不需要先点击某个软件卡片。这个入口只用于新建整包更新包和 Qt IFW 交付包。
发布包页面选择:
| 字段 | 建议 |
| --- | --- |
| 包类型 | Qt IFW 交付包 |
| 文件 | `SimCAE-Delivery-<版本>-windows_x86_64-msvc.zip` |
| 平台 | `windows / x86_64 / msvc` |
| 状态 | 上传校验通过后变为可用 |
操作顺序:
1. 打开“发布包”页面。
2. 直接点击右上角“新建发布包”。
3. 包类型选择“Qt IFW 交付包”。此时普通新建入口只应看到“整包更新包”和“Qt IFW 交付包”。
4. 文件名填写本次交付包文件名,例如 `SimCAE-Delivery-1.1.3-windows_x86_64-msvc.zip`
5. 选择产品、版本线、产品版本、发布和平台。
6. 保存发布包记录。
7. 点击该记录的“上传”。
8. 选择本地生成的交付包 ZIP。
9. 等待上传完成,状态应变为“可用”。
上传成功后,服务端会:
- 校验 ZIP 安全路径和 IFW 结构。
- 校验 `package/``repository/Updates.xml`
- 读取组件清单和组件版本。
- 自动注入 `config/app_config.json`
- 自动写入必要的公钥配置。
- 发布 IFW repository。
- 生成或登记客户门户首次下载的安装器。
上传后建议立刻确认:
```powershell
$Base = "http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc"
(Invoke-WebRequest "$Base/Updates.xml" -UseBasicParsing).Content
```
预期能看到本次版本号、组件 ID 和组件名称。如果这里还是旧版本,先确认发布包状态是否为“可用”,再确认上传时选择的产品、通道和平台是否一致。
## 十、客户下载和安装
客户登录客户门户后,在下载中心下载客户安装器。客户下载到的是 `.exe` 安装器,不是 IFW repository ZIP,也不是开发者上传的交付包 ZIP。
客户安装后,安装目录中应包含:
- `maintenancetool.exe`
- `components.xml`
- `network.xml`
- `view\bin\SimCAE.exe`
- `view\bin\Launcher.exe`
- `view\bin\Updater.exe`
- `view\bin\Bootstrap.exe`
- `view\bin\config\app_config.json`
客户日常启动软件应使用 `Launcher.exe` 或安装器创建的快捷方式。组件更新、添加、移除由 `maintenancetool.exe` 负责。
首次安装建议按这个顺序检查:
1. 打开客户门户。
2. 登录有授权的客户账号。
3. 进入下载中心。
4. 找到对应产品和版本。
5. 点击下载,得到 `SimCAE-<版本>-Windows-installer.exe`
6. 双击安装器,按页面提示完成安装。
7. 安装后进入安装目录,确认 `maintenancetool.exe``components.xml``view\bin\Launcher.exe` 都存在。
8. 双击 `Launcher.exe`,预期能启动 SimCAE。
9. 双击 `maintenancetool.exe`,预期能看到“添加或移除组件”“更新组件”“移除所有组件”。
## 十一、组件更新
这里的“组件”指 `maintenancetool.exe` 里能看到的 Qt IFW 组件,例如 `com.simcae.app``com.simcae.dap`。组件更新就是“只发布某些组件的新版本,或新增一个组件”,让客户后续通过 MaintenanceTool 更新;它不是客户首次安装用的安装器,也不是 Launcher / Updater 用的整包更新 ZIP。
适合使用组件更新的情况:
| 场景 | 应该怎么做 |
| --- | --- |
| 只更新 DAP 插件 | 做一个只包含 `com.simcae.dap` 的组件更新包 |
| 新增示例、模板、插件等可选功能 | 做一个包含新组件的组件更新包 |
| 一次更新几个互相依赖的组件 | 做一个多组件更新包,把这些组件一起放进去 |
| 更新核心程序、Launcher、Updater、Bootstrap 或 `app_config.json` | 更推荐重新发完整 Qt IFW 交付包 |
| 第一次发布某个产品、通道、平台 | 先发完整 Qt IFW 交付包,后面才能发组件更新 |
组件更新包不是单独飘在系统外面的文件。它上传时必须挂到某个产品、某个产品版本、某次发布、某个平台下面。服务端会把它合并到这个产品对应通道和平台的 current repository 中。
### 11.1 组件版本怎么定
组件版本以组件自己的 `package.xml` 为准。比如 DAP 组件的版本写在这里:
- `$Build\package\packages\com.simcae.dap\meta\package.xml`
- XML 节点是 `<Version>1.1.4</Version>`
如果说“组件标签”,这里真正参与更新判断的是组件 ID 和组件版本:组件 ID 来自目录名 `packages/com.simcae.dap`,组件版本来自 `meta/package.xml` 里的 `<Version>`。开发者在维护 IFW package 时就应该把组件拆分、显示名、版本、必选状态和依赖关系写清楚。`repogen.exe` 生成 repository 时,会把这些信息写进 `Updates.xml`。MaintenanceTool 也是根据 `Updates.xml` 里的组件版本判断是否可更新。
当前 SIMCAE 全量打包默认会让所有组件跟随同一个 `$Version`,这个 `$Version` 来自 SIMCAE 仓库的纯数字 Git tag,或者前面文档里的 `$GitVersionShim` 临时版本脚本。例如 `$Version = "1.1.3"` 时,`com.simcae.app``com.simcae.dap` 默认都会变成 `1.1.3`
如果只更新 DAP,不更新核心组件,规则是:
1. 服务器当前 `com.simcae.app``1.1.3``com.simcae.dap``1.1.3`
2. 本次只把 `com.simcae.dap``package.xml` 改成 `1.1.4`
3. 不改 `com.simcae.app``package.xml`,它仍然保持 `1.1.3`
4. 生成只包含 `com.simcae.dap` 的组件更新包。
5. 上传后,服务器 current repository 里应变成 `com.simcae.app=1.1.3``com.simcae.dap=1.1.4`
正式流程里,建议 SIMCAE 打包侧给每个组件提供独立版本参数。当前如果只是本地演示,可以在 `$Build\package\packages\<组件ID>\meta\package.xml` 里调整目标组件的 `<Version>`,然后再生成组件更新包。不要改不更新的组件版本,也不要只改 ZIP 文件名。ZIP 名字里写了 `1.1.4`,但 `package.xml` 仍是 `1.1.3` 时,生成出来的 `Updates.xml` 也会是 `1.1.3`
### 11.2 先确认服务器已有当前仓库
`simcae / stable / windows_x86_64-msvc` 为例:
```powershell
$Base = "http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc"
(Invoke-WebRequest "$Base/Updates.xml" -UseBasicParsing).Content
```
预期能看到当前仓库的组件,例如:
- `<Name>com.simcae.app</Name>`
- `<Version>1.1.3</Version>`
- `<Name>com.simcae.dap</Name>`
- `<Version>1.1.3</Version>`
如果这里访问失败,先不要上传组件更新包,说明服务器还没有这个产品、通道、平台的 current repository。
### 11.3 准备本地组件产物
开发者先按 SIMCAE 自己的规则把组件文件准备到 IFW package staging 里。组件边界应该在开发和打包配置阶段就已经分好;后面的 repository 生成命令只是读取这些组件,不会自动分析文件并替开发者拆组件。当前打包目标会把组件整理到:
- `$Build\package\packages\com.simcae.app`
- `$Build\package\packages\com.simcae.dap`
如果只更新 DAP 插件,先确认 DAP 组件目录存在:
```powershell
Test-Path "$Build\package\packages\com.simcae.dap\meta\package.xml"
Test-Path "$Build\package\packages\com.simcae.dap\data"
```
预期都返回 `True`。同时要确认 `package.xml` 里的版本已经升高:
```powershell
Select-String -LiteralPath "$Build\package\packages\com.simcae.dap\meta\package.xml" -Pattern "<Version>"
```
例如服务器当前 DAP 是 `1.1.3`,本次 DAP 组件更新包应改成 `1.1.4` 或更高。
### 11.4 生成组件更新 ZIP
使用 `-Include` 只把要更新的组件打进 repository。`-Include "com.simcae.dap"` 里的值是组件 ID,也就是 `packages/com.simcae.dap` 这个目录名;它不是 ZIP 标签,也不是版本号。下面以只更新 DAP 为例:
```powershell
$ComponentVersion = "1.1.4"
$PlatformKey = "windows_x86_64-msvc"
$ComponentUpdateRepository = "$Build\ifw-component-update-com.simcae.dap-$ComponentVersion"
$ComponentUpdateZip = "$Build\SimCAE-IFW-ComponentUpdate-com.simcae.dap-$ComponentVersion-$PlatformKey.zip"
Remove-Item -LiteralPath $ComponentUpdateRepository -Recurse -Force -ErrorAction SilentlyContinue
powershell -NoProfile -ExecutionPolicy Bypass -File ".\installer\scripts\build-ifw-repository.ps1" `
-PackageDir "$Build\package" `
-OutputDir $ComponentUpdateRepository `
-ZipFile $ComponentUpdateZip `
-Include "com.simcae.dap"
```
这条命令不会修改 `com.simcae.dap``<Version>`。它只是根据 `-Include` 选择已有组件,把该组件当前 `package.xml` 中写好的版本、显示名和依赖交给 `repogen.exe`,再生成本次组件更新 repository ZIP。
如果一次更新多个组件:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\installer\scripts\build-ifw-repository.ps1" `
-PackageDir "$Build\package" `
-OutputDir "$Build\ifw-component-update-multi-$ComponentVersion" `
-ZipFile "$Build\SimCAE-IFW-ComponentUpdate-multi-$ComponentVersion-$PlatformKey.zip" `
-Include "com.simcae.app","com.simcae.dap"
```
生成后检查 ZIP 对应的展开目录:
```powershell
Test-Path "$ComponentUpdateRepository\Updates.xml"
Get-ChildItem -LiteralPath $ComponentUpdateRepository
Select-String -LiteralPath "$ComponentUpdateRepository\Updates.xml" -Pattern "com.simcae.dap|<Version>|<Dependencies>"
```
单 DAP 更新包的典型结构应类似:
- `Updates.xml`
- `com.simcae.dap/1.1.4meta.7z`
- `com.simcae.dap/1.1.4view.7z`
- `com.simcae.dap/1.1.4view.7z.sha1`
如果 ZIP 解开后外面多套了一层目录,也可以上传;服务端会识别常见外层目录。但推荐让 ZIP 根部直接就是 `Updates.xml` 和组件目录,最不容易出错。
### 11.5 上传组件更新
管理后台操作:
1. 打开“产品版本”,创建本次发布批次版本,例如 `1.1.4`
2. 打开“软件发布”,创建本次发布,通道仍选择 `stable`
3. 打开“发布包”,先点击要更新的软件卡片,进入该软件的发布包视图。
4. 点击“新建组件更新包”。进入某个软件后,包类型固定为“组件更新”,产品固定为当前软件。
5. 发布选择刚创建的 `1.1.4` 发布。
6. 平台选择和 current repository 完全一致的 `windows / x86_64 / msvc`
7. 文件名填写 `SimCAE-IFW-ComponentUpdate-com.simcae.dap-1.1.4-windows_x86_64-msvc.zip`
8. 保存后点击“上传”。
9. 选择上一步生成的 `$ComponentUpdateZip`
10. 上传成功后,发布包状态应变为“可用”。
上传成功后,服务端会把更新组件合并进 current repository,未变化组件保持不变。
### 11.6 上传后确认合并结果
重新读取服务器仓库:
```powershell
$Base = "http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc"
(Invoke-WebRequest "$Base/Updates.xml" -UseBasicParsing).Content
```
预期:
1. 更新过的组件版本变成新版本,例如 `com.simcae.dap``1.1.4`
2. 未更新的组件仍然存在,例如 `com.simcae.app` 还在。
3. 新增组件能出现在 `Updates.xml` 中。
4. 旧版本仓库仍保存在服务端 `releases/<版本>` 目录中,current 指向最新合并结果。
### 11.7 常见失败提示
| 提示含义 | 原因 | 处理 |
| --- | --- | --- |
| 当前产品、通道和平台下还没有可合并的 IFW 当前仓库 | 还没上传过完整交付包 | 先上传完整 Qt IFW 交付包 |
| 组件版本不能倒退或重复 | 上传组件版本小于或等于服务器 current 版本 | 升高组件 `package.xml` 里的版本后重新生成 |
| 组件依赖不存在 | 新组件依赖的组件不在 current 仓库,也不在本次包里 | 先发布依赖组件,或把依赖组件一起打进本次更新包 |
| 缺少组件目录 | `Updates.xml` 声明了组件,但 ZIP 里没有对应目录 | 重新用 `build-ifw-repository.ps1` 生成 |
| 包含未在 `Updates.xml` 声明的组件目录 | ZIP 里多了未声明目录 | 删除多余目录后重新压包 |
| 未包含 `Updates.xml` | 上传的不是 repository ZIP,或服务端没有配置 `SIMCAE_IFW_REPOGEN_PATH` 来从 packages 自动生成 | 上传 repository 形态 ZIP |
更新包失败时,服务端不会破坏原 current repository。
## 十二、换源
服务器地址变化时有两种处理方式:
| 方式 | 适用场景 |
| --- | --- |
| 临时换源命令 | 单台客户机器临时切到新仓库 |
| RepositoryUpdate 批量换源 | 已安装客户软件批量迁移仓库地址 |
换源只影响 `maintenancetool.exe` 访问 IFW repository。Launcher / Updater 的 API 地址来自服务端注入的 `app_config.json`,需要通过新发布包或重新安装包更新。
### 12.1 临时换源
临时换源适合开发、测试、临时排查。它不会永久改安装包里的默认源。
在 SIMCAE 项目根目录执行,假设客户软件安装在 `.tmp\maintenance-installed\SimCAE`
```powershell
$Install = ".\.tmp\maintenance-installed\SimCAE"
$Repo = "http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc/"
powershell -NoProfile -ExecutionPolicy Bypass -File ".\installer\scripts\switch-maintenance-repository.ps1" `
-MaintenanceToolPath "$Install\maintenancetool.exe" `
-RepositoryUrl $Repo `
-Mode Temp `
-Command check-updates `
-ClearCache
```
注意 `$Repo` 必须是仓库根地址,不能写到 `Updates.xml`
- 正确:`http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc/`
- 错误:`http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc/Updates.xml`
### 12.2 批量换源
批量换源适合服务器域名或 IP 变更。做法是在下一次 repository 的 `Updates.xml` 里加入 `RepositoryUpdate`,让 MaintenanceTool 更新组件时顺便替换本机源地址。
示例:把旧源 `http://192.168.1.158:18000/...` 替换为新源 `https://download.simcae.example.com/...`
```powershell
$UpdatesXml = "$Build\ifw-repository\Updates.xml"
$OldRepo = "http://192.168.1.158:18000/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc/"
$NewRepo = "https://download.simcae.example.com/api/v1/client/ifw/repositories/simcae/stable/windows_x86_64-msvc/"
powershell -NoProfile -ExecutionPolicy Bypass -File ".\installer\scripts\write-repository-update.ps1" `
-UpdatesXml $UpdatesXml `
-Action replace `
-OldUrl $OldRepo `
-NewUrl $NewRepo `
-DisplayName "SimCAE stable component repository" `
-ClearExisting
```
写入后重新压 repository 或重新组装 Qt IFW 交付包,再上传到 SimCAE Hub。客户下一次通过 MaintenanceTool 检查或更新组件后,会把仓库地址换成新地址。
## 十三、常见问题
| 现象 | 原因和处理 |
| --- | --- |
| 上传提示缺少 `Updates.xml` | 选择的不是 repository 或交付包结构不对 |
| MaintenanceTool 看不到更新 | 服务器仓库版本没有高于本机 `components.xml` 里的版本 |
| 客户下载不到安装器 | 发布包不是 Qt IFW 交付包,或客户安装器生成/登记失败 |
| Launcher 启动主程序失败 | `SIMCAE_LAUNCH_TOKEN` 和业务主程序编译时 token 不一致 |
| 直接双击 `SimCAE.exe` 被拦截 | 这是启用 Launcher 启动门禁后的预期行为 |
| 可选组件删除后 Updater 报缺文件 | Manifest 中可选组件文件没有标为可选,或组件边界划分不对 |