Windows: official port -- UCRT static build, UTF-8 throughout, Low IL sandbox, ncurses TUI, installer; v0.6.0
Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
This commit is contained in:
+73
-10
@@ -8,6 +8,32 @@ option(CST_BUILD_TUI "Build ncurses/PDCurses TUI" ON)
|
||||
option(CST_NATIVE_ARCH "Pass -march=native to sashidown" OFF)
|
||||
option(CST_BUNDLE_BUSYBOX "Install placeholder/scripts for busybox beside binary" ON)
|
||||
|
||||
if(WIN32)
|
||||
enable_language(RC)
|
||||
# Cross builds link libgcc/winpthreads/curl statically; MSYS2's curl is a DLL.
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(_CST_STATIC_DEFAULT ON)
|
||||
else()
|
||||
set(_CST_STATIC_DEFAULT OFF)
|
||||
endif()
|
||||
option(CST_WIN_STATIC "Link Windows executables with -static" ${_CST_STATIC_DEFAULT})
|
||||
endif()
|
||||
|
||||
# Windows: UTF-8 manifest (activeCodePage, longPathAware) and version resource
|
||||
# on every executable, so tests run with the same process code page as the app.
|
||||
function(cst_win_exe t)
|
||||
if(NOT WIN32)
|
||||
return()
|
||||
endif()
|
||||
target_sources(${t} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/res/windows/cst-agent.rc")
|
||||
target_include_directories(${t} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/res/windows")
|
||||
target_compile_definitions(${t} PRIVATE _WIN32_WINNT=0x0A00)
|
||||
if(CST_WIN_STATIC)
|
||||
target_link_options(${t} PRIVATE -static)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(SD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/sashidown")
|
||||
if(CST_NATIVE_ARCH)
|
||||
set(_SD_MAKE_EXTRA CST_NATIVE_ARCH=1)
|
||||
@@ -15,6 +41,9 @@ else()
|
||||
set(_SD_MAKE_EXTRA "")
|
||||
endif()
|
||||
|
||||
# sashidown has a plain Makefile; CMAKE_MAKE_PROGRAM may be ninja.
|
||||
find_program(CST_MAKE NAMES make gmake mingw32-make REQUIRED)
|
||||
|
||||
# Separate archive for MinGW so Linux host builds keep libsashidown.a
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(SASHIDOWN_A "${SD_DIR}/libsashidown-mingw.a")
|
||||
@@ -26,19 +55,19 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(_MINGW_RANLIB "${CMAKE_RANLIB}")
|
||||
add_custom_command(
|
||||
OUTPUT "${SASHIDOWN_A}"
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a
|
||||
COMMAND ${CST_MAKE} -C "${SD_DIR}" clean
|
||||
COMMAND ${CST_MAKE} -C "${SD_DIR}" libsashidown.a
|
||||
CC=${CMAKE_C_COMPILER} AR=${_MINGW_AR} RANLIB=${_MINGW_RANLIB}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${SD_DIR}/libsashidown.a" "${SASHIDOWN_A}"
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean
|
||||
COMMAND ${CST_MAKE} -C "${SD_DIR}" clean
|
||||
COMMENT "Building libsashidown for MinGW"
|
||||
VERBATIM)
|
||||
else()
|
||||
set(SASHIDOWN_A "${SD_DIR}/libsashidown.a")
|
||||
add_custom_command(
|
||||
OUTPUT "${SASHIDOWN_A}"
|
||||
COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a CC=${CMAKE_C_COMPILER} ${_SD_MAKE_EXTRA}
|
||||
COMMAND ${CST_MAKE} -C "${SD_DIR}" libsashidown.a CC=${CMAKE_C_COMPILER} ${_SD_MAKE_EXTRA}
|
||||
COMMENT "Building libsashidown.a"
|
||||
VERBATIM)
|
||||
endif()
|
||||
@@ -54,6 +83,7 @@ set(CST_CORE_SOURCES
|
||||
src/util.c
|
||||
src/ctl_parse.c
|
||||
src/os_compat.c
|
||||
src/os_win.c
|
||||
src/sandbox.c
|
||||
src/sandbox_exec.c
|
||||
src/keymap.c
|
||||
@@ -82,6 +112,14 @@ set(CST_CORE_SOURCES
|
||||
src/models.c
|
||||
src/metrics.c
|
||||
)
|
||||
if(WIN32)
|
||||
# MinGW has no <regex.h>: musl's TRE engine, UTF-8 aware (vendor/tre-regex)
|
||||
include_directories(vendor/tre-regex)
|
||||
list(APPEND CST_CORE_SOURCES
|
||||
vendor/tre-regex/regcomp.c vendor/tre-regex/regexec.c
|
||||
vendor/tre-regex/regerror.c vendor/tre-regex/tre-mem.c)
|
||||
endif()
|
||||
|
||||
set(CST_SOURCES ${CST_CORE_SOURCES} src/main.c)
|
||||
|
||||
if(CST_BUILD_TUI)
|
||||
@@ -92,6 +130,7 @@ endif()
|
||||
|
||||
add_executable(cst-agent ${CST_SOURCES})
|
||||
add_dependencies(cst-agent sashidown)
|
||||
cst_win_exe(cst-agent)
|
||||
|
||||
target_include_directories(cst-agent PRIVATE
|
||||
include
|
||||
@@ -113,9 +152,26 @@ if(NOT WIN32)
|
||||
target_link_libraries(cst-agent PRIVATE m)
|
||||
endif()
|
||||
|
||||
if(CST_BUILD_TUI)
|
||||
set(CST_WIN_CURSES "ncurses" CACHE STRING "Windows TUI backend: ncurses or pdcurses")
|
||||
set_property(CACHE CST_WIN_CURSES PROPERTY STRINGS ncurses pdcurses)
|
||||
if(CST_BUILD_TUI AND WIN32 AND CST_WIN_CURSES STREQUAL "ncurses")
|
||||
# ncursesw built by scripts/build_third_party_mingw.sh ncurses (or MSYS2's
|
||||
# mingw-w64-ucrt-x86_64-ncurses); headers live in include/ncursesw.
|
||||
find_path(NCURSESW_INCLUDE_DIR ncurses.h PATH_SUFFIXES ncursesw include/ncursesw)
|
||||
find_library(NCURSESW_LIBRARY NAMES ncursesw)
|
||||
if(NOT NCURSESW_INCLUDE_DIR OR NOT NCURSESW_LIBRARY)
|
||||
message(FATAL_ERROR "CST_WIN_CURSES=ncurses needs ncursesw "
|
||||
"(scripts/build_third_party_mingw.sh ncurses), or -DCST_WIN_CURSES=pdcurses")
|
||||
endif()
|
||||
# ncurses.h includes <ncursesw/ncurses_dll.h>, so the parent is needed too
|
||||
get_filename_component(NCURSESW_PARENT_DIR ${NCURSESW_INCLUDE_DIR} DIRECTORY)
|
||||
target_include_directories(cst-agent PRIVATE ${NCURSESW_INCLUDE_DIR} ${NCURSESW_PARENT_DIR})
|
||||
target_link_libraries(cst-agent PRIVATE ${NCURSESW_LIBRARY})
|
||||
target_compile_definitions(cst-agent PRIVATE CST_USE_NCURSES=1 NCURSES_STATIC=1 NCURSES_WIDECHAR=1)
|
||||
message(STATUS "TUI: ncursesw (${NCURSESW_LIBRARY})")
|
||||
elseif(CST_BUILD_TUI)
|
||||
if(WIN32)
|
||||
# Prefer PDCurses (pdcurses / curses). Users may set PDCurses_ROOT.
|
||||
# PDCurses (pdcurses / curses). Users may set PDCurses_ROOT.
|
||||
# PATH_SUFFIXES also covers the layout MSYS2's mingw-w64-pdcurses uses,
|
||||
# which puts the headers under include/pdcurses/ rather than include/.
|
||||
find_path(PDCURSES_INCLUDE_DIR curses.h
|
||||
@@ -156,7 +212,7 @@ endif()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32)
|
||||
# curl Schannel + networking; winmm is needed by PDCurses (PlaySoundW)
|
||||
target_link_libraries(cst-agent PRIVATE
|
||||
ws2_32 crypt32 secur32 bcrypt advapi32 winmm)
|
||||
ws2_32 crypt32 secur32 bcrypt advapi32 user32 gdi32 winmm)
|
||||
endif()
|
||||
|
||||
# --- tests ---
|
||||
@@ -166,7 +222,11 @@ set(_CST_SMALL_TESTS test_ctl_frontier test_ctl_heredoc test_json_baseline
|
||||
test_markdown)
|
||||
foreach(t ${_CST_SMALL_TESTS})
|
||||
if(t STREQUAL test_textsel)
|
||||
add_executable(${t} tests/${t}.c src/textsel.c)
|
||||
add_executable(${t} tests/${t}.c src/textsel.c src/os_compat.c src/os_win.c
|
||||
src/util.c)
|
||||
if(WIN32)
|
||||
target_link_libraries(${t} PRIVATE advapi32 user32)
|
||||
endif()
|
||||
elseif(t STREQUAL test_markdown)
|
||||
add_executable(${t} tests/${t}.c)
|
||||
target_include_directories(${t} PRIVATE "${SD_DIR}/include")
|
||||
@@ -186,6 +246,7 @@ foreach(t ${_CST_SMALL_TESTS})
|
||||
if(NOT WIN32)
|
||||
target_link_libraries(${t} PRIVATE m)
|
||||
endif()
|
||||
cst_win_exe(${t})
|
||||
add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endforeach()
|
||||
|
||||
@@ -194,7 +255,8 @@ foreach(t test_llm_body test_sse_decode test_apply_patch test_session_roundtrip
|
||||
test_shell test_diff test_tools_fs test_registry
|
||||
test_undo test_config test_models test_sandbox
|
||||
test_keymap test_shell_compact test_skills
|
||||
test_plugins test_mcp test_subagent)
|
||||
test_plugins test_mcp test_subagent test_lowil
|
||||
test_win_utf8 test_os_dir)
|
||||
add_executable(${t} tests/${t}.c ${CST_CORE_SOURCES})
|
||||
target_include_directories(${t} PRIVATE include "${CJSON_DIR}")
|
||||
target_compile_definitions(${t} PRIVATE _GNU_SOURCE)
|
||||
@@ -202,8 +264,9 @@ foreach(t test_llm_body test_sse_decode test_apply_patch test_session_roundtrip
|
||||
if(NOT WIN32)
|
||||
target_link_libraries(${t} PRIVATE m)
|
||||
else()
|
||||
target_link_libraries(${t} PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32)
|
||||
target_link_libraries(${t} PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32 user32)
|
||||
endif()
|
||||
cst_win_exe(${t})
|
||||
add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endforeach()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user