Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions 3rdparty/velopack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,39 @@

许可证:MIT(见上游仓库 LICENSE)。

当前仅 vendor Windows x64 资产:
- `include/Velopack.h`、`include/Velopack.hpp`:官方 C/C++ 头文件(C++ 为 C API 的薄封装)。
- `lib/velopack_libc_win_x64_msvc.dll`:动态库本体。
- `lib/velopack_libc_win_x64_msvc.dll.lib`:MSVC 导入库。
当前 vendor 的资产:

macOS / Linux / Windows arm64 等资产后续按平台补充(zip 内其余文件未 vendor)。
- Windows x64:
- `include/Velopack.h`、`include/Velopack.hpp`:官方 C/C++ 头文件(C++ 为 C API 的薄封装)。
- `lib/velopack_libc_win_x64_msvc.dll`:动态库本体。
- `lib/velopack_libc_win_x64_msvc.dll.lib`:MSVC 导入库。
- macOS(universal x86_64 + arm64):
- `lib/libvelopack_libc.dylib`:来自 zip 内 `lib/velopack_libc_osx.dylib`,经下述三步
预处理后按 `lib` 前缀命名(见「macOS 预处理」)。`lib-static/` 下的 `.a` 未 vendor:
dylib 已预链 Rust 依赖,免去手动补 framework/link 参数。

Linux / Windows arm64 等资产后续按平台补充(zip 内其余文件未 vendor)。

## macOS 预处理

上游 dylib 直接链接会产生两个问题,vendor 时已做如下处理(复现命令):

1. 上游 install id 是 CI 构建机的绝对路径(`/Users/runner/work/...`),直接链接会把
该路径写进可执行文件的依赖表,运行时找不到库。改名 + 重写 id 为 `@rpath/`:
```bash
mv libvelopack_libc_osx.dylib libvelopack_libc.dylib # lib 前缀使 -lvelopack_libc 可解析
install_name_tool -id @rpath/libvelopack_libc.dylib libvelopack_libc.dylib
```
加载名必须与磁盘文件名一致(loader 按 install id 的文件名在各 rpath 目录查找),
故 id 带 lib 前缀,不能对齐 Windows 的无前缀 `velopack_libc.dll`。
2. `install_name_tool` 改 id 会使原有 linker-signed 签名失效,arm64 上签名损坏的
dylib 会被 AMFI 拒载,必须 ad-hoc 重签:
```bash
codesign --force --sign - --timestamp=none libvelopack_libc.dylib
```

注意:与 Windows 的改名时机不同——Windows 在安装期把 DLL 改名为
`velopack_libc.dll`(见 `xmake/targets/stem.lua`),macOS 因链接期/运行期文件名
必须与磁盘文件一致,只能在 vendor 时改好提交。

dylib 的 `LC_BUILD_VERSION` minos 为 11.0,低于应用自身要求,无兼容性约束。
Binary file added 3rdparty/velopack/lib/libvelopack_libc.dylib
Binary file not shown.
2 changes: 1 addition & 1 deletion TeXmacs/progs/texmacs/menus/preferences-widgets.scm
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@
) ;list
;; 更新通道(Stable/Beta 单选 combo):真实存储单值 update-channel,
;; 写值经 set-field 特例路由到 updater-switch-channel 的两次确认。
;; 仅 Velopack 更新器平台(Windows 安装版)显示。
;; 仅 Velopack 更新器平台(Windows / macOS arm64 安装版)显示。
(if (use-plugin-updater?)
(list (list (pref-update-channel)
"Update channel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@
(check (length (caddr (tab-ref meta "keyboard"))) => (if (os-macos?) 15 14))
(check (length (caddr (tab-ref meta "mathematics"))) => 11)
(check (length (caddr (tab-ref meta "convert"))) => 0)
(check (length (caddr (tab-ref meta "other"))) => (if (os-macos?) 17 15))
;; other: 平台基础字段 + Velopack 更新器开启时多 1 个 Update channel 字段。
;; 用 use-plugin-updater? 而非硬编码,osx-x64 未接入 velopack 时仍正确。
(check (length (caddr (tab-ref meta "other")))
=>
(+ (if (os-macos?) 17 15) (if (use-plugin-updater?) 1 0))
) ;check
) ;let
) ;define

Expand Down
166 changes: 166 additions & 0 deletions devel/0519.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# 0519: macOS arm64 Velopack 打包与自动更新

## 2026/08/27 macOS arm64 接入 Velopack

### What(本次改动做了什么)

把 macOS arm64 接入与 Windows 相同的 Velopack 发布/更新链路:

1. vendor macOS libc 运行时动态库(上游 release 1.2.0 的 universal
`velopack_libc_osx.dylib`,前置任务单独提交);
2. xmake/C++ 运行时集成:`USE_PLUGIN_VELOPACK` 扩展到 macOS arm64,
启动钩子/更新器后端/feed 平台段全部放开平台 guard;
3. release 脚本(`stage_velopack.lua`/`pack_velopack.lua`)增加 macOS
分支,产出 vpk 原生 `.pkg` 安装器 + portable `.zip` + full/delta
nupkg + `releases.<channel>.json` feed。

### Why(为什么这么做)

- 0512 立下的目标是三平台共用 Velopack 的发布版本/渠道/客户端更新 API,
各平台产出各自安装格式;macOS 是第二个接入的平台。
- 现有 macOS 发布(.dmg)无自动更新;Velopack delta 增量 + 应用内更新
与 Windows 侧体验拉齐。
- 发布物选 vpk 原生 `.pkg` + `.zip`(不做 DMG):`.pkg` 走系统安装流程
(用户可选 /Applications 或 ~/Applications),更新链路天然支持;DMG
可后续从 portable zip 自行生成,不在本任务。

### How(怎么做的)

按提交顺序分四步:

1. **任务文档**(本文件)。
2. **vendor macOS dylib(前置任务,单独提交)**:从
`velopack_libc_1.2.0.zip` 取 `lib/velopack_libc_osx.dylib`
(universal x86_64+arm64),改名 `libvelopack_libc.dylib`(lib 前缀
使 `add_links("velopack_libc")` 可解析;macOS 链接期文件名须与磁盘
名一致,故 vendor 时改名,与 Windows 安装期改名不同),
`install_name_tool -id @rpath/libvelopack_libc.dylib` 重写 install id
(上游是 CI 构建路径;id 必须与磁盘文件名一致,loader 按该文件名在
rpath 目录查找),随后 `codesign --force --sign -` 重签(改 id
会破坏原签名,arm64 上签名损坏的 dylib 会被 AMFI 拒载)。
lib-static 的 `.a` 不 vendor:dylib 已预链 Rust 依赖,免手动补
framework。
3. **C++/xmake 运行时集成**:
- `xmake/velopack.lua`:`add_velopack_runtime()` 平台分发,macOS
arm64 直链 dylib;`velopack_startup_test` 目标扩到 mac。
- `xmake/velopack.lua` 增加 `velopack_libc` shared target(on_build 拷
预编译产物,targetdir 定在 `<stem targetdir>/Frameworks`):qt.widgetapp
的部署规则会把 shared 依赖的 targetfile 自动拷进 .app 的
`Contents/Frameworks`,裸二进制(dev 运行/CI scheme 测试走
`target:targetfile()`,bundle 外)靠自带 `@executable_path/Frameworks`
rpath 找到同一份。自写 after_build 拷贝不可行——qt 部署规则会
`os.tryrm` 整个 .app 重建,时机上必然被清掉。
- `xmake/targets/libmogan.lua`:`USE_PLUGIN_VELOPACK = win x64 或
macosx arm64`,不按构建模式 gate(CI releasedbg 跑 scheme 测试,
模式 gate 会使测试期望随模式漂移)。
- `research.cpp` 启动钩子、`tm_updater.cpp`/`tm_velopack.cpp` 后端
选择 guard 扩为 `OS_WIN || OS_MACOS`。
- `tm_velopack.cpp` feed 平台段改编译期宏 `UPDATER_FEED_PLATFORM`
(`win-x64`/`osx-arm64`/`osx-x64`),替换硬编码 `win-x64`;不走
config.h.xmake(单消费者不值得动全局配置头)。
- `preferences-widgets-test.scm`:other tab 字段数期望改为
`(+ (if (os-macos?) 17 15) (if (use-plugin-updater?) 1 0))`,
不硬编码 18(osx-x64 未开启时仍正确)。
4. **release 脚本 macOS 分支**:
- `stage_velopack.lua`:按 `os.host()` 分发;定位
`build/macosx/*/release/MoganSTEM.app`,`ditto` 拷贝(保 Qt
framework 符号链接,xmake `os.cp` 默认解引用会打坏 bundle);
从 `packages/macos/Info.plist.in` 生成真 plist(替换
`@STEM_NAME@`/`@XMACS_VERSION@`,不依赖 stem_packager——它
release-only、依赖 create-dmg、会产 DMG);ad-hoc 签 staging
副本;结构与禁发文件校验。
- `pack_velopack.lua`:vpk 定位加 mac 候选路径;args 用
`--runtime osx-arm64`、`--mainExe MoganSTEM`、`--packDir` 指向
`.app`;显式传 `--channel`(vpk mac 默认 channel 是 `osx`,
不传会拆渠道);签名参数 `VPK_SIGN_APP_IDENTITY` 等五个环境变量
透传(本阶段默认不签,凭据留给 CI);产物从
`assets.<channel>.json` 发现 `.pkg`/`.zip` 后按
`MoganSTEM-v<版本>-arm64-<渠道>-Setup.pkg`/`-Portable.zip` 改名;
`prune_old_full` 后处理平台无关,不动。

feed 平台段为 `osx-arm64`(服务端 `/api/v1/public/update/osx-arm64`
目录不在本任务范围)。版本纪律沿用 0512 的 `rc.N` 预发布格式。

### 不做什么

- CI workflow 改造(`cd_on_macos_arm64.yml` 仍走 package.scm 产 DMG,
接入 velopack 是后续任务);
- DMG 产物;osx-x64(dylib 为 universal,后续加成本低);
- 正式 Developer ID 签名/公证(环境变量已接好,凭据后续配)。

### 涉及文件

- `3rdparty/velopack/lib/libvelopack_libc.dylib`(新)、`3rdparty/velopack/README.md`
- `xmake/velopack.lua`、`xmake/targets/stem.lua`、`xmake/targets/libmogan.lua`
- `src/Mogan/Research/research.cpp`、`src/Plugins/Updater/tm_updater.cpp`、
`src/Plugins/Updater/tm_velopack.cpp`
- `tools/velopack/velopack_startup_test.cpp`
- `TeXmacs/progs/texmacs/menus/preferences-widgets.scm`(注释)、
`TeXmacs/progs/texmacs/menus/tests/preferences-widgets-test.scm`
- `tools/release/stage_velopack.lua`、`tools/release/pack_velopack.lua`

### 如何测试

```bash
# 运行时集成(releasedbg,对齐 macOS CI)
xmake f -c -m releasedbg --yes && xmake b stem
otool -L build/macosx/arm64/releasedbg/MoganSTEM.app/Contents/MacOS/MoganSTEM | grep velopack
xmake r updater-test && xmake r preferences-widgets-test
xmake b velopack_startup_test && xmake r velopack_startup_test
xmake run --group=scheme_tests

# 打包(release)
xmake f -c -m release --yes && xmake b stem && xmake i stem
xmake l tools/release/stage_velopack.lua
xmake l tools/release/pack_velopack.lua
```

## 2026/08/27 验证结果

### 运行时集成(releasedbg,对齐 ci-macos-arm64.yml)

- `otool -L` 裸二进制与 .app 内二进制均记录 `@rpath/libvelopack_libc.dylib`;
dylib 两处落位:`<targetdir>/Frameworks/`(裸二进制,dev 运行与 CI scheme
测试走 `target:targetfile()`)与 `.app/Contents/Frameworks/`(qt 部署规则
按 shared 依赖拷贝)。
- `xmake r updater-test`:9 correct, 0 failed——macOS 首次以 `tm_velopack`
激活状态跑该测试(Windows CI 不跑 scheme 测试,Linux 编的是基类空实现)。
- `xmake r preferences-widgets-test`:434 correct, 0 failed(other tab
18 字段,含 Update channel)。
- `xmake r velopack_startup_test`:输出 `velopack startup hook: ok`。
- `xmake run --group=scheme_tests` 全组合计 1152 correct, 0 failed。

### 打包(release,本机 ad-hoc,未接正式签名)

- stage:4203 文件 / 420.4 MB;`plutil -lint` 通过;CFBundleIdentifier=
app.mogan、CFBundleExecutable=MoganSTEM、版本 2026.3.2。
- vpk 1.2.0(dotnet tool,需 `DOTNET_ROOT=/opt/homebrew/opt/dotnet/libexec`)
实测 macOS 产物名与 Windows 同约定:`Mogan-<channel>-Setup.pkg` /
`Mogan-<channel>-Portable.zip`;改名后为
`MoganSTEM-v2026.3.2-arm64-stable-{Setup.pkg,Portable.zip}`(pkg 168MB /
zip 172MB),assets.stable.json 同步更新。注意 assets.json 是顶层数组
(字段 `RelativeFileName`),与 releases.json 的 `{Assets:[...]}` 结构不同。
- 无签名参数时 vpk 明确告警跳过签名/公证(Package will not be signed or
notarized),流程不失败。
- delta:同 outputDir 下以 `VPK_VERSION=2026.3.3` 二次 pack,产出
`Mogan-2026.3.3-stable-delta.nupkg`(4141 unchanged / 1 patched / 10
removed),增量链可用(测试产物已清理)。

### 端到端

- Portable.zip 解压到 /tmp 直接运行:headless 冒烟
`(use-plugin-updater?)` ⇒ `#t`、`(updater-state)` ⇒ 0(IDLE),进程正常
退出——bundle 内 dylib 加载、`$TEXMACS_PATH` 从 bundle 解析均正常。
- GUI `open` 启动存活(越过 2s 的 updater-initialize 窗口,feed 尚未就绪
也不崩);首选项 → Other 可见 Update channel。
- .pkg 解包检查:Distribution 引用 app.mogan@2026.3.2 主 bundle,
`enable_currentUserHome="true"` 支持 ~/Applications 安装。

### 后续(非本任务)

- CI 接入(cd_on_macos_arm64.yml 或新 workflow)+ Developer ID 签名/公证
(`VPK_SIGN_APP_IDENTITY` 等五个环境变量已接好);
- 服务端 `/api/v1/public/update/osx-arm64` feed;
- 本机跑 vpk 需 dotnet + `dotnet tool install -g vpk`;brew 安装的 dotnet
不在 vpk 默认 runtime 搜索路径,pack 脚本已自动探测注入
`DOTNET_ROOT=/opt/homebrew/opt/dotnet/libexec`,无需手动 export。
6 changes: 3 additions & 3 deletions src/Mogan/Research/research.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
#include "tm_url.hpp"
#include "tm_window.hpp"

#if defined(OS_WIN)
#if defined(OS_WIN) || defined(OS_MACOS)
#include "Velopack.hpp"
#endif

Expand Down Expand Up @@ -165,8 +165,8 @@ int
main (int argc, char** argv) {

// Velopack 启动钩子:处理待安装的更新(无安装时为空操作)。
// 必须早于任何系统初始化与参数解析,且仅限 Windows。
#if defined(OS_WIN)
// 必须早于任何系统初始化与参数解析Windows 与 macOS 安装版均需要)
#if defined(OS_WIN) || defined(OS_MACOS)
Velopack::VelopackApp::Build ().Run ();
#endif

Expand Down
4 changes: 2 additions & 2 deletions src/Plugins/Updater/tm_updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include "config.h"
#include "scheme.hpp"

#if defined(OS_WIN) && defined(USE_PLUGIN_VELOPACK)
#if defined(USE_PLUGIN_VELOPACK) && (defined(OS_WIN) || defined(OS_MACOS))
#include "tm_velopack.hpp"
#endif

Expand All @@ -22,7 +22,7 @@ tm_updater::instance () {
static tm_updater* _instance= NULL;

if (!_instance) {
#if defined(OS_WIN) && defined(USE_PLUGIN_VELOPACK)
#if defined(USE_PLUGIN_VELOPACK) && (defined(OS_WIN) || defined(OS_MACOS))
_instance= new tm_velopack ();
#else
_instance= new tm_updater ();
Expand Down
21 changes: 17 additions & 4 deletions src/Plugins/Updater/tm_velopack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include "tm_configure.hpp"

#if defined(USE_PLUGIN_VELOPACK) && defined(OS_WIN)
#if defined(USE_PLUGIN_VELOPACK) && (defined(OS_WIN) || defined(OS_MACOS))

#include "preferences.hpp"
#include "string.hpp"
Expand Down Expand Up @@ -45,13 +45,25 @@ feed_base_url () {
return "https://liiistem.cn";
}

// feed 平台段:与 vpk pack 的 --runtime 一致,服务端按平台目录分发各自的
// releases.<channel>.json 与 nupkg。只在本编译单元消费,不走 config.h。
#if defined(OS_WIN)
#define UPDATER_FEED_PLATFORM "win-x64"
#elif defined(OS_MACOS) && defined(__aarch64__)
#define UPDATER_FEED_PLATFORM "osx-arm64"
#elif defined(OS_MACOS) && defined(__x86_64__)
#define UPDATER_FEED_PLATFORM "osx-x64"
#else
#error "unsupported velopack feed platform"
#endif

// 完整 feed URL = base URL + 按社区版/商业版(IS_COMMUNITY 宏)选定的路径段
static std::string
feed_url (const std::string& base) {
#ifdef IS_COMMUNITY
return base + "/api/v1/public/update/win-x64";
return base + "/api/v1/public/update/" UPDATER_FEED_PLATFORM;
#else
return base + "/api/v1/public/commercial/update/win-x64";
return base + "/api/v1/public/commercial/update/" UPDATER_FEED_PLATFORM;
#endif
}

Expand Down Expand Up @@ -389,4 +401,5 @@ tm_velopack::applyUpdate () {
return true;
}

#endif // defined (USE_PLUGIN_VELOPACK) && defined (OS_WIN)
#endif // defined (USE_PLUGIN_VELOPACK) && (defined (OS_WIN) || defined
// (OS_MACOS))
Loading
Loading