Files
msime-windows/.github/workflows/ci.yml
T
luo jiyinandfanlusky acc25f40e6 fix(installer): 在最终数据目录保存云候选选择 (#972)
* fix(installer): 在最终数据目录保存云候选选择

安装器在数据目录确定后检查已有配置。升级迁移遇到配置冲突时停止,避免覆盖目标配置。

首次安装以 UTF-8 写入云候选选择,并在写入失败时报告错误。

增加安装时序检查和 Windows 安装测试矩阵。

Refs #931

* test(installer): 用 Inno 探针验证云候选配置写回

主安装器与探针共用云候选配置函数。

Windows CI 执行五个隔离的首次安装和升级场景。

Refs #931

* test(installer): 接受 Inno 配置文件的 CRLF 行尾

测试按完整的 cloud_candidates 行检查结果。失败时打印实际字段值。

Refs #931

* fix(installer): check config migration conflict before stopping IME

* test(installer): clear expected conflict exit code

* test(installer): narrow source check to migration order

* fix(installer): 云候选写回失败不再中断安装,迁移可重试

- 写回「不启用云候选」失败时只记日志并提示,挪到必需的安装后步骤之后
- 两处 config.toml 内容相同时视为上次迁移的残留,允许重试
- config.toml 没搬过去时中止安装,避免新目录装上出厂配置
- 数据目录页就检查配置冲突
- ShouldSkipPage 不再写全局状态
- 探针复用生产的迁移判断与顺序,补齐改目录首次安装、重试和迁移失败场景

---------

Co-authored-by: fanlusky <1730976608@qq.com>
2026-10-15 23:00:34 +08:00

398 lines
17 KiB
YAML
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: CI
on:
push:
branches: [develop, main]
pull_request:
workflow_dispatch:
merge_group:
permissions:
contents: read
concurrency:
group: ci-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
quality:
uses: ./.github/workflows/quality.yml
lock:
name: Product inputs
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
# The engine is vendored in-tree, so a plain checkout already carries the contracts this job
# compares against. opencc and cpp-pinyin are still submodules but nothing here builds.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check the shared dictionary validator
run: python3 scripts/check-dictionary-contract.py
- name: Check product-lock contract
run: python3 scripts/check-product-lock-contract.py
# The voice activity detector is committed rather than downloaded, and it is executed inside
# the IME process. The engine's own voice.yml used to re-check its digest; that workflow left
# with the submodule, so the check moves here rather than disappearing.
- name: Check the committed voice model against its recorded digest
run: |
echo '1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3 engine/voice/assets/models/silero_vad.onnx' | sha256sum --check --strict
- name: Test rejection of changed or mismatched inputs
run: python3 -m unittest discover -s tests -p 'test_product_lock.py'
- name: Test local environment setup in a moved checkout
run: python3 -m unittest discover -s tests -p 'test_prepare_env.py'
- name: Test the automatic release CI gate
run: python3 -m unittest discover -s tests -p 'test_release_pr_ci.py'
- name: Read the reviewed lock
id: lock
run: python3 scripts/product_lock.py outputs --github-output "$GITHUB_OUTPUT"
windows:
name: Windows ${{ matrix.name }}
runs-on: windows-2025
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- name: x64
architecture: x64
triplet: x64-windows-static
- name: Win32
architecture: Win32
triplet: x86-windows-static
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Restore dependency binaries
id: vcpkg-cache
uses: ./.github/actions/vcpkg-cache
with:
scope: tip-${{ matrix.triplet }}
- name: Install dependencies
shell: pwsh
working-directory: windows
run: vcpkg install --triplet ${{ matrix.triplet }}
- name: Save dependency binaries before compiling
if: steps.vcpkg-cache.outputs.matched-key != format('{0}-complete', steps.vcpkg-cache.outputs.key)
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.vcpkg-cache.outputs.path }}
key: ${{ steps.vcpkg-cache.outputs.key }}-complete
- name: Configure
shell: pwsh
run: >
cmake -S windows -B windows/build -A ${{ matrix.architecture }}
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }}
- name: Build
shell: pwsh
run: cmake --build windows/build --config Release --parallel
- name: Check the Release binary and symbols
shell: pwsh
run: ./scripts/ci/check-tsf-dll.ps1 -BuildDir 'windows/build'
- name: Test the wire contract on this architecture
run: ctest --no-tests=error --test-dir windows/build -C Release --output-on-failure
# Architecture-independent, so it runs once rather than on both matrix legs.
- name: Check the installer language file lands where ISCC looks
if: ${{ matrix.name == 'x64' }}
shell: pwsh
run: ./scripts/ci/check-inno-language.ps1
# MSIME-Linux's ci.yml already reasons about this: "the frontend layer had none -- and that is the
# layer that parses untrusted input", after an audit of the Windows frontend turned up a batch of
# races and null dereferences that the Debug build did not catch. Linux acted on that; this
# repository had no sanitizer coverage at all.
#
# Sanitizing the whole server would mean a sanitized Boost, curl, sqlite and WebView2 on a Windows
# runner. Instead the host-independent parsing and policy code builds as its own tiny target with
# no dependencies, which runs under ASan and UBSan on Linux in seconds. It covers less than a full
# sanitized build would; it covers it on every pull request.
sanitizers:
name: Sanitizers
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Configure with AddressSanitizer and UndefinedBehaviorSanitizer
run: >
cmake -S server/tests/portable -B server/tests/portable/build
-DCMAKE_BUILD_TYPE=Debug -DMSIME_PORTABLE_TESTS_SANITIZE=ON
- name: Build
run: cmake --build server/tests/portable/build --parallel
- name: Test
working-directory: server/tests/portable/build
run: ctest --output-on-failure
server:
name: Server
needs: lock
runs-on: windows-2025
timeout-minutes: 90
# sccache caches individual MSVC compiles across runs so an incremental change recompiles only
# what it touched instead of the whole server + engine tree. It needs SCCACHE_GHA_ENABLED for
# every step that shells out to the compiler (configure probes it too, not just build).
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Restore dependency binaries
id: vcpkg-cache
uses: ./.github/actions/vcpkg-cache
with:
scope: server-x64-windows-and-static-md
# The Visual Studio generator finds MSVC on its own; Ninja does not, so put cl.exe on PATH
# before configuring. Defaults to the x64 host/target, matching the build below.
- name: Set up MSVC environment
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
- name: Verify direct MSVC fallback
shell: pwsh
run: ./scripts/ci/test-sccache-fallback.ps1
- name: Set up sccache
id: sccache
continue-on-error: true
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: Install Boost
shell: pwsh
run: ./scripts/ci/install-boost.ps1
- name: Save Boost binaries before configuring
if: steps.vcpkg-cache.outputs.matched-key != format('{0}-complete', steps.vcpkg-cache.outputs.key) && steps.vcpkg-cache.outputs.matched-key != format('{0}-boost', steps.vcpkg-cache.outputs.key)
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.vcpkg-cache.outputs.path }}
key: ${{ steps.vcpkg-cache.outputs.key }}-boost
# 刻意不叫 build:CMakeLists.txt 曾经写死 ${CMAKE_SOURCE_DIR}/build/vcpkg_installed,换任何别的名字都会链接失败。构建到别处才能保证这条不会悄悄退回去。
#
# Ninja Multi-Config instead of the Visual Studio generator: sccache plugs in through
# CMAKE_<LANG>_COMPILER_LAUNCHER, which CMake honours only for the Ninja and Makefile
# generators. "Multi-Config" keeps the --config Release / bin/Release layout the later steps
# (check-server-binaries.ps1, ctest -C Release) already depend on.
- name: Configure
shell: pwsh
run: |
$cmakeArgs = @(
'-S', 'server', '-B', 'server/build-release', '-G', 'Ninja Multi-Config',
"-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake",
'-DVCPKG_TARGET_TRIPLET=x64-windows'
)
$cmakeArgs += @(./scripts/ci/cmake-sccache-args.ps1 -SetupOutcome '${{ steps.sccache.outcome }}')
cmake @cmakeArgs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Save dependency binaries before compiling
if: steps.vcpkg-cache.outputs.matched-key != format('{0}-complete', steps.vcpkg-cache.outputs.key)
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.vcpkg-cache.outputs.path }}
key: ${{ steps.vcpkg-cache.outputs.key }}-complete
- name: Build
shell: pwsh
run: cmake --build server/build-release --config Release --parallel
- name: Report sccache hit rate
if: ${{ !cancelled() && steps.sccache.outcome == 'success' }}
continue-on-error: true
shell: pwsh
run: sccache --show-stats
- name: Check the Release binaries and symbols
shell: pwsh
run: ./scripts/ci/check-server-binaries.ps1 -BuildDir server/build-release/bin/Release
- name: Test with the product's exact data
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./scripts/ci/test-server.ps1 -ServerRoot server -StagingRoot "$env:RUNNER_TEMP/product-data" -BuildDir build-release
# The Server job builds the engine as a library with BUILD_TESTING off, so the engine's own suite
# only ever ran on developer machines. It covers behaviour server/tests cannot reach, such as the
# user journal's per-thread connections and the rule that closing it releases every cached
# handle into the data directory. Built standalone from engine/vcpkg.json, tested against the
# same locked dictionaries as the Server.
engine:
name: Engine
needs: lock
runs-on: windows-2025
timeout-minutes: 60
# Same sccache setup as the Server job. SCCACHE_IGNORE_SERVER_IO_ERROR lets a compile fall back
# to plain cl.exe when the sccache server is unreachable, so a GitHub cache outage costs time
# instead of failing this required check.
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Restore dependency binaries
id: vcpkg-cache
uses: ./.github/actions/vcpkg-cache
with:
scope: engine-x64-windows
- name: Set up MSVC environment
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
- name: Set up sccache
id: sccache
continue-on-error: true
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
# Keep Ninja Multi-Config: CMake ignores CMAKE_<LANG>_COMPILER_LAUNCHER under the Visual
# Studio generator, so switching back to -A x64 would silently disable sccache.
- name: Configure
shell: pwsh
run: |
$cmakeArgs = @(
'-S', 'engine', '-B', 'engine/build', '-G', 'Ninja Multi-Config',
"-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake",
'-DVCPKG_TARGET_TRIPLET=x64-windows', '-DBUILD_TESTING=ON'
)
$cmakeArgs += @(./scripts/ci/cmake-sccache-args.ps1 -SetupOutcome '${{ steps.sccache.outcome }}')
cmake @cmakeArgs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Save dependency binaries before compiling
if: steps.vcpkg-cache.outputs.matched-key != format('{0}-complete', steps.vcpkg-cache.outputs.key)
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.vcpkg-cache.outputs.path }}
key: ${{ steps.vcpkg-cache.outputs.key }}-complete
- name: Build
shell: pwsh
run: cmake --build engine/build --config Release --parallel
- name: Report sccache hit rate
if: ${{ !cancelled() && steps.sccache.outcome == 'success' }}
continue-on-error: true
shell: pwsh
run: sccache --show-stats
- name: Test with the product's exact data
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./scripts/ci/test-engine.ps1 -BuildDir engine/build -StagingRoot "$env:RUNNER_TEMP/product-data"
gui:
name: GUI framework
runs-on: windows-2025
timeout-minutes: 30
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check library ownership
run: python ui/scripts/check-boundary.py
- name: Configure
shell: pwsh
run: cmake -S ui -B ui/build -A x64
- name: Build
shell: pwsh
run: cmake --build ui/build --config Release --parallel
- name: Test
shell: pwsh
run: ctest --no-tests=error --test-dir ui/build -C Release --verbose --timeout 60
pages:
name: Settings page
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The pages ship a copy of the engine's web contract. Before the consolidation the product
# lock kept that copy honest by pinning UiHtml and the engine together; now that the engine is
# a directory of this repository, the copy is checked against engine/contracts/webview
# directly and a plain checkout is enough to do it.
- name: Check the bundled contract against the engine
run: python3 ui-html/scripts/sync-contracts.py --check
- name: Set up pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: pnpm
cache-dependency-path: ui-html/webview2/settings/ime-settings/pnpm-lock.yaml
- name: Install
working-directory: ui-html/webview2/settings/ime-settings
run: pnpm install --frozen-lockfile
- name: Test
working-directory: ui-html/webview2/settings/ime-settings
run: pnpm test
- name: Test candidate DOM rendering
run: node --test server/tests/test-candidate-dom-render.cjs
# Same command the release workflow runs. Until it was added here it was only ever exercised
# during a release, so a break surfaced while cutting a version.
- name: Build
working-directory: ui-html/webview2/settings/ime-settings
run: pnpm build
- name: Check the output the installer collects
working-directory: ui-html/webview2/settings/ime-settings
run: |
test -f dist/index.html
echo "dist contents:"
ls -la dist
# External skins are plain data, so the checks are the manifest contract the Server catalog
# enforces plus the example's installer; both run against the example and the skins/ collection.
skins:
name: Skins
runs-on: windows-2025
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
- name: Validate the example manifests and referenced assets
working-directory: examples/skin-examples
run: python -m unittest discover -s tests -v
- name: Validate the skins collection against the same contract
working-directory: examples/skin-examples
env:
MSIME_SKINS_DIR: ../../skins
run: python -m unittest discover -s tests -v
- name: Test install, activation and repeated installation in a temporary user directory
working-directory: examples/skin-examples
run: ./tests/install.ps1
packaging:
name: Package contents
runs-on: windows-2025
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Test full and light package contents without registering or installing
run: ./installer/tests/package-files.ps1
- name: Test that the installer preserves user data on upgrade
run: ./installer/tests/preserve-user-data.ps1
- name: Check installer cloud choice and config preservation
run: python ./installer/tests/test_cloud_choice.py
- name: Test cloud choice with Inno Setup
run: ./installer/tests/cloud-choice.ps1
- name: Test that bundled skins back up changed user copies before overwriting
run: ./installer/tests/bundled-skins.ps1
- name: Test that upgrades keep the desktop neural model shipped by 0.9.x
run: ./installer/tests/retired-desktop-model.ps1
- name: Stop local packaging after failed component builds
run: ./installer/tests/build-failures.ps1
- name: Test local Server test entrypoints in a moved checkout
run: ./tests/server-test-entrypoints.ps1