Files
msime-windows/.github/workflows/release.yml
T
Evan Hu 32e722c176 ci: build every component from the one checkout
The product lock existed because five repositories had to be pinned to reproduce one product. Four of them are directories now, so a commit of this repository already pins them and there is nothing left for the lock to say about them. What it still records is what the product consumes from outside: the engine submodule, the helpcodes and the dictionary release.

CI gains a job per component instead of a reusable workflow that checked out locked commits and rebuilt them. The release workflow no longer stages five sibling checkouts under historical names and no longer carries a second sparse checkout for its own scripts; Prepare-PackageFiles.ps1 already took the source directory names as parameters, so it is told the new ones, plus where the product-wide notice now lives.

Two checks the removed cross-repository pins used to provide are replaced in kind: the pages are verified against the engine submodule's web contract in CI rather than by locking UiHtml and Engine together, and the engine bump triage that Server carried moves here and now also says so when a bump leaves the lock behind.
2026-09-06 02:35:49 +09:00

428 lines
17 KiB
YAML

name: Release
# Building and publishing only ever happens on workflow_dispatch. The code signing certificate
# has a limited number of signatures, so a release costs something real and is not something a merge
# to main should spend on its own. The push trigger only keeps release-please's pull request current
# and green; merging that pull request creates the draft, and dispatching this workflow against the
# draft's tag is what builds, signs and publishes it.
on:
push:
branches:
- main
workflow_dispatch:
inputs:
tag:
description: Draft release tag to build, sign and publish, for example v2026.9.1
required: true
type: string
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
prepare:
name: Prepare release metadata
runs-on: ubuntu-24.04
permissions:
# actions: write lets check-release-pr.sh dispatch ci.yml on the release branch, which is the
# only trigger GITHUB_TOKEN is allowed to fire.
actions: write
contents: write
pull-requests: write
outputs:
# Never true on push: see the comment above the triggers. release-please still creates the
# draft release there, it just does not get built until somebody asks for it.
should_publish: ${{ github.event_name == 'workflow_dispatch' }}
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || steps.release.outputs.tag_name }}
version: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.version || steps.release.outputs.version }}
target_sha: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.target_sha || steps.release.outputs.sha }}
steps:
- name: Check out release automation
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Prepare release
if: ${{ github.event_name == 'push' }}
id: release
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Test the release pull request
if: ${{ github.event_name == 'push' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: bash scripts/ci/check-release-pr.sh
- name: Validate requested draft release
if: ${{ github.event_name == 'workflow_dispatch' }}
id: requested_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ inputs.tag }}
run: bash scripts/ci/validate-draft-release.sh
dependencies:
name: Load the release product lock
needs: prepare
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: ubuntu-24.04
outputs:
helpcode_sha: ${{ steps.lock.outputs.helpcode_sha }}
dictionary_tag: ${{ steps.lock.outputs.dictionary_tag }}
steps:
- name: Read dependencies from the release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.target_sha }}
persist-credentials: false
# verify-published is a release gate and is deliberately absent from ci.yml: a pull request
# legitimately locks a branch commit while a change lands in the engine and here at once, and
# enforcing reachability there would deadlock the landing it protects.
- name: Validate and export the product lock
id: lock
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/product_lock.py verify-contracts .
python3 scripts/product_lock.py verify-published
python3 scripts/product_lock.py outputs --github-output "$GITHUB_OUTPUT"
tsf:
name: Build TSF ${{ matrix.name }}
needs: [prepare, dependencies]
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: windows-2025
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- name: x64
architecture: x64
triplet: x64-windows-static
build_dir: windows/build64-release
- name: Win32
architecture: Win32
triplet: x86-windows-static
build_dir: windows/build32-release
steps:
- name: Check out release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.target_sha }}
submodules: recursive
persist-credentials: false
- name: Stamp the version resource
shell: pwsh
run: |
python scripts/apply_version.py
python scripts/apply_version.py --check
- name: Install dependencies
shell: pwsh
working-directory: windows
run: vcpkg install --triplet ${{ matrix.triplet }}
# CMakePresets.json points at a developer machine's vcpkg, so the presets cannot be used
# here. This mirrors what .github/workflows/ci.yml already does, into the build directory
# layout that the installer's Prepare-PackageFiles.ps1 expects.
- name: Configure
shell: pwsh
run: >
cmake -S windows -B ${{ matrix.build_dir }} -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 ${{ matrix.build_dir }} --config Release --parallel
- name: Check the DLL the installer expects
shell: pwsh
run: ./scripts/ci/check-tsf-dll.ps1 -BuildDir '${{ matrix.build_dir }}'
- name: Test the wire contract on this architecture
run: ctest --test-dir ${{ matrix.build_dir }} -C Release --output-on-failure
- name: Upload the DLL
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tsf-${{ matrix.name }}
path: ${{ matrix.build_dir }}/Release/MetasequoiaImeTsf.dll
if-no-files-found: error
server:
name: Build Server
needs: [prepare, dependencies]
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: windows-2025
timeout-minutes: 90
steps:
- name: Check out release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.target_sha }}
submodules: recursive
persist-credentials: false
- name: Install Boost
shell: pwsh
run: ./scripts/ci/install-boost.ps1
- name: Check out locked helpcodes for integration tests
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: metasequoiaime/MSIME-HelpCode
ref: ${{ needs.dependencies.outputs.helpcode_sha }}
path: MetasequoiaImeHelpCode
persist-credentials: false
# The package job stages these outputs into the installer's expected directory.
- name: Configure
shell: pwsh
run: >
cmake -S server -B server/build-release -A x64
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
-DVCPKG_TARGET_TRIPLET=x64-windows
- name: Build
shell: pwsh
run: cmake --build server/build-release --config Release --parallel
- name: Check the binaries the installer requires
shell: pwsh
run: ./scripts/ci/check-server-binaries.ps1 -BuildDir server/build-release/bin/Release
- name: Test Server with the release's exact dictionaries
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./scripts/ci/test-server.ps1 -ServerRoot server -HelpcodeRoot MetasequoiaImeHelpCode -StagingRoot "$env:RUNNER_TEMP/product-data" -BuildDir build-release
- name: Upload the Server build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server
path: server/build-release/bin/Release
if-no-files-found: error
ui:
name: Build settings page
needs: [prepare, dependencies]
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Check out release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.target_sha }}
persist-credentials: false
submodules: true
- name: Verify the shipped pages carry the engine's contract
run: python3 ui-html/scripts/sync-contracts.py --check
- name: Set up pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
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 and build the settings page
working-directory: ui-html/webview2/settings/ime-settings
run: |
pnpm install --frozen-lockfile
pnpm build
- name: Upload the web assets
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: webview2
path: |
ui-html/webview2/candwnd
ui-html/webview2/ftb
ui-html/webview2/menu
ui-html/webview2/shared
ui-html/webview2/settings/ime-settings/dist
if-no-files-found: error
package:
name: Package and publish installer
needs: [prepare, dependencies, tsf, server, ui]
# Stated explicitly even though skipped needs would already skip this job. This is the only job
# that spends a signature, so the condition that stops it belongs here rather than being a
# consequence of four other jobs' conditions that a later edit could quietly undo.
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: windows-2025
timeout-minutes: 60
permissions:
contents: write
env:
DICTIONARY_TAG: ${{ needs.dependencies.outputs.dictionary_tag }}
# download-dictionaries.sh stages MetasequoiaImeDict/out under this root, and
# Prepare-PackageFiles.ps1 reads its dictionary directory from the same place. Before the
# consolidation this was a synthetic tree holding five sibling checkouts under their
# historical names; now the only sources that still arrive from outside are the helpcodes and
# the dictionary, so they land beside the components in the one checkout.
STAGING_ROOT: .
steps:
- name: Check out the release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.prepare.outputs.target_sha }}
persist-credentials: false
- name: Verify the release commit is on main
shell: bash
run: bash scripts/ci/verify-commit-on-main.sh
- name: Check out MSIME-HelpCode
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: metasequoiaime/MSIME-HelpCode
ref: ${{ needs.dependencies.outputs.helpcode_sha }}
path: MetasequoiaImeHelpCode
persist-credentials: false
- name: Download the TSF DLLs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: tsf-x64
path: windows/build64-release/Release
- name: Download the 32-bit TSF DLL
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: tsf-Win32
path: windows/build32-release/Release
- name: Download the Server build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: server
path: server/build-release/bin/Release
- name: Download the web assets
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: webview2
path: ui-html/webview2
- name: Download the dictionaries
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bash scripts/ci/download-dictionaries.sh
- name: Decide the signing mode
id: signing
shell: pwsh
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}
run: ./scripts/ci/detect-release-signing.ps1
- name: Collect the installer payload
shell: pwsh
working-directory: installer
env:
TARGET_VERSION: ${{ needs.prepare.outputs.version }}
run: >
pwsh -File ./Prepare-PackageFiles.ps1 -TargetVersion $env:TARGET_VERSION
-RepoRoot (Resolve-Path '..').Path
-TsfDirectory windows -ServerDirectory server -UiHtmlDirectory ui-html -NoticesDirectory .
- name: Embed the exact product dependency manifest
env:
WINDOWS_COMMIT: ${{ needs.prepare.outputs.target_sha }}
run: >
python scripts/product_lock.py manifest
--windows-commit "$env:WINDOWS_COMMIT"
--output installer/app_data/product-manifest.json
- name: Sign the packaged binaries
if: ${{ steps.signing.outputs.signing_enabled == 'true' }}
shell: pwsh
working-directory: installer
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}
TIMESTAMP_URL: ${{ vars.WINDOWS_SIGNING_TIMESTAMP_URL || 'http://timestamp.digicert.com' }}
run: ../scripts/ci/sign-binaries.ps1 -Path 'server_exe', 'tsf_dll' -Recurse
- name: Install the Chinese language file for Inno Setup
shell: pwsh
run: ./scripts/ci/install-inno-language.ps1
- name: Compile the installer
shell: pwsh
working-directory: installer
run: pwsh -File ./Compile-Installer.ps1
- name: Sign the installer
if: ${{ steps.signing.outputs.signing_enabled == 'true' }}
shell: pwsh
working-directory: installer
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}
TIMESTAMP_URL: ${{ vars.WINDOWS_SIGNING_TIMESTAMP_URL || 'http://timestamp.digicert.com' }}
TARGET_VERSION: ${{ needs.prepare.outputs.version }}
run: ../scripts/ci/sign-binaries.ps1 -Path "Output/MetasequoiaIME_Setup_v$env:TARGET_VERSION.exe"
- name: Name the asset and record its checksum
id: asset
shell: pwsh
working-directory: installer
env:
TARGET_VERSION: ${{ needs.prepare.outputs.version }}
ASSET_SUFFIX: ${{ steps.signing.outputs.asset_suffix }}
run: ../scripts/ci/name-installer-asset.ps1
- name: Upload the installer as a workflow artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: installer
path: |
${{ steps.asset.outputs.asset_path }}
installer/app_data/product-manifest.json
if-no-files-found: error
- name: Revalidate the draft release target
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ needs.prepare.outputs.tag_name }}
TARGET_SHA: ${{ needs.prepare.outputs.target_sha }}
run: bash scripts/ci/revalidate-draft-release.sh
- name: Upload and publish the release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ needs.prepare.outputs.tag_name }}
ASSET_PATH: ${{ steps.asset.outputs.asset_path }}
ASSET_NAME: ${{ steps.asset.outputs.asset_name }}
ASSET_SHA256: ${{ steps.asset.outputs.asset_sha256 }}
SIGNING_ENABLED: ${{ steps.signing.outputs.signing_enabled }}
PRODUCT_MANIFEST: installer/app_data/product-manifest.json
run: bash scripts/ci/publish-release.sh