Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ research/ui/*.js text eol=lf
research/ui/reference_sources/*.py.txt text eol=lf
*.ps1 text eol=crlf
third_party/conpty/LICENSE text eol=lf
third_party/msvc_runtime/NOTICE.txt text eol=lf
experiments/core/fixtures/*.bin binary
23 changes: 20 additions & 3 deletions .github/workflows/qt-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
with:
python-version: '3.12'
- name: Verify preparation failure contracts
run: python -m unittest tools.test_prepare_qt_build -v
- name: Prepare manifest-pinned Qt qtbase and Microsoft ConPTY
run: python -m unittest tools.test_prepare_qt_build tools.test_prepare_msvc_runtime tools.test_msvc_runtime -v
- name: Prepare manifest-pinned Qt qtbase, Microsoft ConPTY and CRT
run: python -m tools.prepare_qt_build
- name: Preserve path-free dependency preparation identity
if: always()
Expand All @@ -39,9 +39,26 @@ jobs:
-DTERMFORGE_BUILD_CANDIDATE_ADAPTER=OFF `
"-DPython3_EXECUTABLE=$((Get-Command python).Source)" `
"-DQt6_DIR=$dependencyRoot/qt/lib/cmake/Qt6" `
"-DTERMFORGE_CONPTY_BACKEND_DIR=$dependencyRoot/conpty"
"-DTERMFORGE_CONPTY_BACKEND_DIR=$dependencyRoot/conpty" `
"-DTERMFORGE_MSVC_RUNTIME_DIR=$dependencyRoot/crt"
- name: Build Qt product and independent smoke
run: cmake --build build/qt-cpp --config Release --target termforge_qt_shell qt_cpp_shell_smoke --parallel 2
- name: Record complete path-free runtime manifests
run: |
python tools/msvc_runtime.py manifest --directory build/qt-cpp/qt-bin/Release --executable termforge_qt_shell.exe
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python tools/msvc_runtime.py manifest --directory build/qt-cpp/qt-smoke-bin/Release --executable qt_cpp_shell_smoke.exe
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Preserve runtime file identities without binaries
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: qt-cpp-runtime-manifests
retention-days: 14
if-no-files-found: warn
path: |
build/qt-cpp/qt-bin/Release/runtime-manifest.json
build/qt-cpp/qt-smoke-bin/Release/runtime-manifest.json
- name: Run Qt offscreen smoke in its owned Job
run: ctest --test-dir build/qt-cpp -C Release -R '^qt_cpp_shell$' --no-tests=error --verbose
# Keep this configuration independent from the default/system-backend and
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,10 @@ if(TERMFORGE_BUILD_QT_SHELL)
if(NOT WIN32)
message(FATAL_ERROR "The first Qt local-shell migration entry requires Windows x64")
endif()
find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter)
find_package(Qt6 6.10.3 EXACT REQUIRED COMPONENTS Widgets)
include(cmake/ConptyBackend.cmake)
include(cmake/MsvcRuntime.cmake)
get_target_property(qt_core_dll Qt6::Core IMPORTED_LOCATION_RELEASE)
get_filename_component(qt_binary_directory "${qt_core_dll}" DIRECTORY)
find_program(qt_deploy_tool windeployqt HINTS "${qt_binary_directory}" NO_DEFAULT_PATH REQUIRED)
Expand All @@ -355,6 +357,7 @@ if(TERMFORGE_BUILD_QT_SHELL)
_WIN32_WINNT=0x0A00 QT_NO_KEYWORDS)
termforge_warnings(${target})
termforge_use_bundled_conpty(${target})
termforge_use_msvc_runtime(${target})
add_custom_target(${target}_font_bundle
COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_FILE_DIR:${target}>/assets/fonts"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
Expand Down
54 changes: 54 additions & 0 deletions cmake/MsvcRuntime.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# App-local deployment of the manifest-pinned Microsoft desktop CRT.
include_guard(GLOBAL)

function(termforge_use_msvc_runtime target)
if(NOT WIN32 OR NOT MSVC OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "The fixed Microsoft CRT requires an MSVC Windows x64 build")
endif()
if(NOT TARGET ${target})
message(FATAL_ERROR "CRT target does not exist: ${target}")
endif()
if(NOT TERMFORGE_MSVC_RUNTIME_DIR OR NOT IS_ABSOLUTE "${TERMFORGE_MSVC_RUNTIME_DIR}")
message(FATAL_ERROR "TERMFORGE_MSVC_RUNTIME_DIR must name the absolute fixed-runtime directory")
endif()

# The compiler's 19.x self-version is not the installed VC Tools 14.x
# directory version. Use the selected toolset metadata, then the actual
# selected compiler path when CMake leaves its default toolset unset.
if(CMAKE_VS_PLATFORM_TOOLSET_VERSION)
set(toolset_version "${CMAKE_VS_PLATFORM_TOOLSET_VERSION}")
else()
file(TO_CMAKE_PATH "${CMAKE_CXX_COMPILER}" compiler_path)
if(compiler_path MATCHES "/VC/Tools/MSVC/(14[.][0-9]+[.][0-9]+)/bin/[^/]+/[^/]+/cl[.]exe$")
set(toolset_version "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR "Cannot identify the selected VC Tools directory version")
endif()
endif()
if(NOT toolset_version MATCHES "^14[.][0-9]+[.][0-9]+$")
message(FATAL_ERROR "The selected VC Tools version must have the form 14.minor.build")
endif()

get_filename_component(repository "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/.." ABSOLUTE)
set(deploy_tool "${repository}/tools/msvc_runtime.py")
set(manifest "${repository}/third_party/msvc_runtime/manifest.json")
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${deploy_tool}" verify
--source "${TERMFORGE_MSVC_RUNTIME_DIR}" --toolset-version "${toolset_version}"
RESULT_VARIABLE runtime_verification
OUTPUT_QUIET ERROR_QUIET TIMEOUT 30)
if(NOT runtime_verification STREQUAL "0")
message(FATAL_ERROR "Fixed CRT input identity or VC Tools compatibility validation failed")
endif()

# Always validate and repair app-local files, including builds that do not
# relink. No installer or network operation belongs in this build step.
add_custom_target(${target}_msvc_runtime
COMMAND "${Python3_EXECUTABLE}" "${deploy_tool}" deploy
--source "${TERMFORGE_MSVC_RUNTIME_DIR}"
--destination "$<TARGET_FILE_DIR:${target}>"
--toolset-version "${toolset_version}"
DEPENDS "${deploy_tool}" "${manifest}"
VERBATIM)
add_dependencies(${target} ${target}_msvc_runtime)
endfunction()
1 change: 1 addition & 0 deletions docs/EVIDENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
|---|---|
| 自主 C++ 双缓冲存储与文档 epoch 基础 | [固定结果](../experiments/core/results/2026-10-29-buffer-state-r2/README.md)、[验证身份](../experiments/core/results/2026-10-29-buffer-state-r2/validation.json):拥有状态、共享 ID、隐藏帧 resize 回滚与跨缓冲句柄失效;Qt/candidate 关闭配置的完整 50 项及 12 条内部 skip 分列。未接 private CSI,不是原版模式、Qt 产品路线或完整 #27 验收 |
| Qt cmd 标签与退出前回收 | [固定结果](../experiments/shell/results/2026-10-28-qt-tabs/README.md)、[验证身份](../experiments/shell/results/2026-10-28-qt-tabs/validation.json):双 cmd 隔离、失败页、关闭与四个实际事件循环边界;Qt 1 项和其他 49 项诊断及内部跳过分列,旧中断轮不拼接 |
| Qt app-local 固定微软 CRT 与隔离 Windows 加载 | [固定结果](../experiments/shell/results/2026-10-29-qt-crt-deploy/README.md)、[完整运行时与验证身份](../experiments/shell/results/2026-10-29-qt-crt-deploy/validation.json):原 `cf8` 52 文件首次加载失败与仅追加 CRT/原许可后的成功分列;新源码构建、原生窗口、有限 cmd/关闭、offscreen 及 Job 清理独立记录。不是完整发行、跨 DPI/IME 或物理呈现验收 |
| Qt 正式入口的独立 hosted 构建 | [固定结果](../experiments/shell/results/2026-10-28-qt-cpp-ci/README.md)、[验证身份](../experiments/shell/results/2026-10-28-qt-cpp-ci/validation.json):固定 SDK、VS 2026 hosted 与真实 cmd 的 offscreen / OwnedJob 检查;原依赖换行和 generator 失败保留。没有原生上屏、完整部署哈希或产品包验收 |
| 自主 C++ + Qt Widgets + 固定微软 ConPTY 正式目标 | [ADR-0007](adr/0007-bundled-microsoft-conpty.md) / [ADR-0008](adr/0008-cpp-core-qt-widgets.md) 记录用户确认;新组合的实现与验证另行留证,不引用旧默认或 Alacritty 通过结果代替 |
| 既有默认/Alpha:自主 C++ + 系统 ConPTY + Win32 | R1 限定总验收和有限 VT/Unicode 子集;与当前 Qt/固定后端目标组合分列 |
Expand Down
2 changes: 2 additions & 0 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

自主 C++ 核心的 [#130 双缓冲存储基础](../experiments/core/results/2026-10-29-buffer-state-r2/README.md)已在独立模块边界实现拥有状态、交换激活、共享行 ID、双帧物理 resize 与文档 epoch 隔离,旧位置、选择、搜索及 viewport 不会在切换返回后复活。该基础尚未接入 private CSI;1049 的样式、重复进入清空及历史文档策略与完整 [#27](https://github.com/JavaZeroo/termforge/issues/27)另行验收,不表示产品已支持备用屏或 Qt 交互已迁移。

Qt 构建显式准备并部署[固定微软 CRT 14.51.36247.0](../third_party/msvc_runtime/README.md),不安装到 System32。隔离 Windows 的[限定加载与 cmd 验证](../experiments/shell/results/2026-10-29-qt-crt-deploy/README.md)保留原包失败、原 52 文件不变且仅追加 CRT/原许可后的成功,以及新源码包的独立验证;它不解除完整产品打包、Qt/第三方分发审计、实际 DPI/IME 或物理呈现的验收缺口。

官方参考的基础文字行为已按 [R1-01 合同](research/windterm-r1-text-contract.md)完成两组各两轮的自动取证;这为独立实现提供行为依据,产品能力仍按下表分别验收。

| 能力 | 当前状态与边界 | 目标差距 | 路标 |
Expand Down
33 changes: 33 additions & 0 deletions experiments/shell/results/2026-10-29-qt-crt-deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Qt / 固定 CRT 的隔离 Windows 运行验证

本结果只覆盖 [#138](https://github.com/JavaZeroo/termforge/issues/138) 的 Windows x64 app-local 运行库依赖,父项为 [#35](https://github.com/JavaZeroo/termforge/issues/35)。微软 CRT 固定为 **14.51.36247.0**,来源、逐层提取身份、十个 DLL 和原许可见 [依赖说明](../../../../third_party/msvc_runtime/README.md)。完整记录为 [validation.json](validation.json),不包含远程地址、VM UUID、账户、XML、口令、截图、DLL 或原始日志。

## 原包失败与纯 CRT 修订

第一次实际 Qt 加载前,隔离 Windows 11 Enterprise Evaluation 26H2 x64(26300.9457,2 vCPU / 6 GiB)的五个 System32 `vcruntime/msvcp140` 文件不存在,VC++ runtime uninstall 产品列表为空。guest 已安装 QGA/QXL/SPICE 测试控制工具,不能称为未经工具安装的裸系统。

原始输入固定在 `cf8b40b2e44f9e475cfc42b5008d46c670ff1756`:产品 25 个运行时文件、smoke 27 个;原 ZIP SHA-256 为 `967a98e24b1ff56165d46064096ad90804660b8eb99dfac59bfd3dc6b1012a37`。首次在真实交互 Session 2 创建产品进程,resume 前确认继承自有请求 Job,产品立即以 **`0xC0000135`** 退出,没有可见窗口。观测脚本 exit 0 只表示成功取得结果;不表示产品通过,也未确定 loader 最先缺少的单个 DLL 名称。

修订目录保持原 52 个文件逐一哈希不变,只在每个运行时目录添加十个固定 CRT DLL、微软原许可及 NOTICE。修订产品在 `windows` 平台下 5 秒后仍运行,WinAPI 枚举到一个可见 Qt 窗口,向自有窗口发送 WM_CLOSE 后产品 exit 0,无强制终止。System32 五个文件前后仍不存在,VC++ 安装记录前后仍为空。[完整文件映射](causal-runtime-files.json)同时列出原 52 个文件和新增 24 个文件,避免用重编译后的成功解释这组纯 CRT 因果比较。

同一 `cf8` 修订 smoke 使用 `QT_QPA_PLATFORM=windows`,测试进程通过有界 GO gate 后调用实际 cmd:独立预期 `750`、双标签 `750/427`、关闭后 `778`、失败页后的 `147`、退出 37/41、错误文字、正文墨迹、输入隔离和真实 `app.exec()` 的关闭前回收均通过,smoke exit 0。它是进程内 Qt 测试输入与图像检查,没有物理键盘或用户呈现端点验收。

三个请求的 Job 分别记录 active/total/after-active 为 **0/5/0、0/7/0、0/28/0**,根句柄已回收、清理验证通过,没有超时或请求强制终止。观测脚本、产品原生退出、限定 smoke、Job 清理和 `presented_done=null` 在 JSON 中分列。

## 新源码的独立集成

随后从干净提交 **`c5a47adc42db592c4c1966062cbd06b12f0f88ed`** 使用全新离线准备目录和全新 CMake 构建目录验证接入,与上面的 `cf8` 因果对照分开。Qt 6.10.3 官方 MSVC 2022 x64 qtbase 的 4,358 文件树哈希仍为 `a35ab6fe76b62e4d9bd6d3c6b0d476c63583070cea5c9118ef5bb02042ffe266`;固定 ConPTY 为 1.25.260930003。实际构建工具目录为 VC Tools **14.42.34433**,编译器自报 19.42.34433.0,CMake 3.31.0 / Python 3.12.10 / Release x64,candidate adapter 关闭。

自动准备、配置、两个 Qt 显式 target 构建及 offscreen CTest **1/1** 通过,外层 Job active 0 / total 26 / terminated 0。配置缺少显式 CRT 目录时 exit 1;每次构建重新校验并部署 CRT,单独删除自有输出中的一个已核对 DLL 后,无须 EXE 重链接即可恢复相同哈希。实际工具集无法识别或新于固定 CRT 时拒绝构建,不从编译器 `19.x` 推测 `14.x`,不下载或回退系统 CRT。

[产品清单](product-runtime.json)包含 37 个文件,[smoke 清单](smoke-runtime.json)包含 39 个文件;清单排除自身。独立传入 guest 的包另外保留两份清单,因此冻结核对 78 个文件。新源码包也独立完成 Session 2 的原生窗口加载/WM_CLOSE 以及 `windows` 插件下的有限 cmd smoke,退出和清理检查同样分列;最终对原包、纯 CRT 修订包和新源码包全部重新核对哈希。

新增 CRT 的 21 项公共 CLI 子进程测试覆盖离线准备、CAB 切片/内部成员、DLL/许可/NOTICE、工具集上限、部署失败前不改变目标、完整清单和重复生成稳定性,另保留 6 项既有 main CLI 边界测试。普通与 `-O` 均 **27/27** 通过,其中五项在真实 NTFS junction 上检查包外文件、输入/输出根及祖先路径均被拒绝。内部审查的两组红例保留了旧实现错误接受 junction、甚至枚举到包外合成文件的事实;修复后在解引用或下降目录前拒绝 reparse point。

修订包准备脚本另有一次独立失败:PowerShell 5.1 的 `PSObject.Properties.Count` 不产生所需数组计数,在复制和启动前退出;改用 `@(...).Count` 后才生成修订包。最早新增 CLI 测试也曾因工具文件尚不存在而出现 setup error,该记录不冒充行为红例;之后的显式 `unimplemented` 行为红例与通过记录独立保存。私有原始记录的大小、哈希和请求脚本原文/实际 staged 字节身份均在 validation 中保留。

## 来源与限制

[原包 PE imports](original-pe-imports.json)核对 42 个 PE 文件;新源码的 [产品 imports](integrated-product-pe-imports.json)和 [smoke imports](integrated-smoke-pe-imports.json)分别核对 30/32 个 PE 文件。EXE、Qt Core/Gui/Widgets 的 MSVCP/VCRUNTIME 依赖与固定 CRT 文件集对应;固定 ConPTY 文件对的直接依赖另列。这里只记录静态导入,不把它当作动态行为或全部许可的证明。

此项不交付完整 R2 发行版、Qt/第三方完整分发审计、实际跨 DPI 产品工作流、IME/Find/rename、全应用矩阵或性能等价。没有数值性能验收,测试耗时不作性能指标;`presented_done` 保持 null。CI 与双轴审查的实时结果由 PR 维护,不回填为本地已验证事实。
Loading
Loading