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.
428 lines
17 KiB
YAML
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
|