* 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>
398 lines
17 KiB
YAML
398 lines
17 KiB
YAML
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
|