Prepare-PackageFiles 断言并拷贝两个模型与授权声明;release workflow 在收集产物前 运行 fetch-neural-model.ps1。
623 lines
29 KiB
YAML
623 lines
29 KiB
YAML
name: Release
|
|
|
|
# Releasing is automatic. A push to main that carries a fix or a feat ends with a published installer, without anybody merging or dispatching anything: this workflow refreshes release-please's pull request, tests it, merges it, turns the merge into a draft release, then builds, signs and publishes that draft.
|
|
#
|
|
# The whole cycle runs inside the one run that the original push started. land-release-pr.sh merges with GITHUB_TOKEN, whose pushes do not start workflow runs, so the merge cannot produce a second Release run queued behind this one on the concurrency group.
|
|
#
|
|
# This spends code signing signatures on every releasable merge to main -- one per payload EXE/DLL plus one for the installer, so roughly fifteen per release rather than one. The certificate allows 3000 signatures a month, so the quota is still not what constrains this; what it spends is release hygiene -- see docs/product-release.md.
|
|
#
|
|
# workflow_dispatch stays, and is now a repair path rather than the normal one. It builds an existing draft, which is what a run that failed after creating the draft leaves behind.
|
|
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:
|
|
# A push refreshes release-please metadata while workflow_dispatch builds a
|
|
# selected draft. They must not block each other on the same branch lock.
|
|
group: release-${{ github.event_name }}-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
prepare:
|
|
name: Prepare release metadata
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60
|
|
permissions:
|
|
# Read the PR-triggered run; merging stays scoped to GITHUB_TOKEN.
|
|
actions: read
|
|
contents: write
|
|
pull-requests: write
|
|
outputs:
|
|
should_publish: ${{ github.event_name == 'workflow_dispatch' || steps.target.outputs.release_created == 'true' }}
|
|
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || steps.target.outputs.tag_name }}
|
|
version: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.version || steps.target.outputs.version }}
|
|
target_sha: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.target_sha || steps.target.outputs.target_sha }}
|
|
steps:
|
|
- name: Check out release automation
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# Separate release metadata from PR updates to preserve release-please ordering.
|
|
# Recover an already-merged release before calculating the next PR, matching release-please's
|
|
# normal releases-then-pull-requests ordering.
|
|
- name: Require the release metadata credential
|
|
if: ${{ github.event_name == 'push' }}
|
|
env:
|
|
RELEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
run: |
|
|
if [[ -z "$RELEASE_TOKEN" ]]; then
|
|
echo '::error::RELEASE_PLEASE_TOKEN must allow contents and workflows writes to create release tags.'
|
|
exit 1
|
|
fi
|
|
|
|
- name: Recover releases from previously merged pull requests
|
|
if: ${{ github.event_name == 'push' }}
|
|
id: release_pr
|
|
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
|
with:
|
|
# release-please reads and writes the repository's default branch unless told otherwise, and that is now develop. The release branch is main: without this the version pull request, the changelog and the tag all land on develop, and the branch this workflow waits for (release-please--branches--main--*) is never created.
|
|
target-branch: main
|
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
skip-github-pull-request: true
|
|
|
|
- name: Refresh the release pull request
|
|
# When recovery found a release to publish, do not create the next
|
|
# release PR in the same run. That PR belongs to the next main push.
|
|
if: ${{ github.event_name == 'push' && steps.release_pr.outputs.release_created != 'true' }}
|
|
id: release_update
|
|
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
|
with:
|
|
target-branch: main
|
|
# A trusted credential starts normal PR checks without manual workflow approval.
|
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
skip-github-release: true
|
|
|
|
- name: Test and merge the release pull request
|
|
if: ${{ github.event_name == 'push' }}
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GH_REPO: ${{ github.repository }}
|
|
run: bash scripts/ci/land-release-pr.sh
|
|
|
|
- name: Create the release for the merged pull request
|
|
if: ${{ github.event_name == 'push' }}
|
|
id: release
|
|
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
|
with:
|
|
target-branch: main
|
|
# Creating a tag at a historical workflow-changing commit needs workflows:write.
|
|
# Only GITHUB_TOKEN merges to main, preventing a second Release run.
|
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
skip-github-pull-request: true
|
|
|
|
- name: Resolve the release this run publishes
|
|
if: ${{ github.event_name == 'push' }}
|
|
id: target
|
|
env:
|
|
FIRST_CREATED: ${{ steps.release_pr.outputs.release_created }}
|
|
FIRST_TAG: ${{ steps.release_pr.outputs.tag_name }}
|
|
FIRST_VERSION: ${{ steps.release_pr.outputs.version }}
|
|
FIRST_SHA: ${{ steps.release_pr.outputs.sha }}
|
|
SECOND_CREATED: ${{ steps.release.outputs.release_created }}
|
|
SECOND_TAG: ${{ steps.release.outputs.tag_name }}
|
|
SECOND_VERSION: ${{ steps.release.outputs.version }}
|
|
SECOND_SHA: ${{ steps.release.outputs.sha }}
|
|
run: bash scripts/ci/resolve-auto-release.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
|
|
timeout-minutes: 60
|
|
outputs:
|
|
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-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: [self-hosted, Windows, X64, msime-windows]
|
|
env:
|
|
# Each self-hosted runner has a different default vcpkg installation.
|
|
# Keep every matrix job aligned with the registry selected below even
|
|
# when GitHub schedules x64 and Win32 on different runner instances.
|
|
VCPKG_ROOT: ${{ matrix.vcpkg_root }}
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: x64
|
|
architecture: x64
|
|
triplet: x64-windows-static
|
|
build_dir: windows/build64-release
|
|
vcpkg_root: E:\msime-runner\vcpkg-1
|
|
- name: Win32
|
|
architecture: Win32
|
|
triplet: x86-windows-static
|
|
build_dir: windows/build32-release
|
|
vcpkg_root: E:\msime-runner\vcpkg-2
|
|
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
|
|
|
|
# Some runner instances leave _work owned by SYSTEM while the job itself
|
|
# runs as `runner`. checkout still succeeds, so the first plain git call
|
|
# is what exits 128 on dubious ownership, minutes into the job.
|
|
#
|
|
# The self-hosted jobs are separate runner instances on one machine, so
|
|
# they share C:\Users\runner\.gitconfig and reach this step in the same
|
|
# second. Whoever loses that race gets "could not lock config file". Read
|
|
# the list back before each attempt so a peer's write counts as success.
|
|
- name: Trust the runner workspace
|
|
shell: pwsh
|
|
run: |
|
|
$workspace = $env:GITHUB_WORKSPACE -replace '\\', '/'
|
|
foreach ($attempt in 1..10) {
|
|
$trusted = git config --global --get-all safe.directory
|
|
if ($trusted -contains $workspace) { exit 0 }
|
|
git config --global --add safe.directory $workspace
|
|
if ($LASTEXITCODE -eq 0) { exit 0 }
|
|
Start-Sleep -Milliseconds (200 * $attempt)
|
|
}
|
|
throw "Could not add $workspace to the global safe.directory list"
|
|
|
|
- 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: |
|
|
& "$env:VCPKG_ROOT\\vcpkg.exe" 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: Verify the selected vcpkg root
|
|
shell: pwsh
|
|
run: |
|
|
$expected = '${{ matrix.vcpkg_root }}'
|
|
if ($env:VCPKG_ROOT -ne $expected) {
|
|
throw "VCPKG_ROOT is '$env:VCPKG_ROOT'; expected '$expected' for ${{ matrix.name }} on runner '$env:RUNNER_NAME'"
|
|
}
|
|
|
|
- name: Configure
|
|
shell: pwsh
|
|
run: >
|
|
cmake -S windows -B ${{ matrix.build_dir }} -A ${{ matrix.architecture }}
|
|
-DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg_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 TSF binary and symbols 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 TSF binary and symbols
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: tsf-${{ matrix.name }}
|
|
# Upload the output directory so download-artifact restores its contents directly into
|
|
# the layout Prepare-PackageFiles.ps1 expects.
|
|
path: ${{ matrix.build_dir }}/Release
|
|
if-no-files-found: error
|
|
|
|
server:
|
|
name: Build Server
|
|
needs: [prepare, dependencies]
|
|
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
|
|
runs-on: [self-hosted, Windows, X64, msime-windows]
|
|
env:
|
|
VCPKG_INSTALLATION_ROOT: E:\\msime-runner\\vcpkg-3
|
|
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
|
|
|
|
# Some runner instances leave _work owned by SYSTEM while the job itself
|
|
# runs as `runner`. checkout still succeeds, so the first plain git call
|
|
# is what exits 128 on dubious ownership, minutes into the job.
|
|
#
|
|
# The self-hosted jobs are separate runner instances on one machine, so
|
|
# they share C:\Users\runner\.gitconfig and reach this step in the same
|
|
# second. Whoever loses that race gets "could not lock config file". Read
|
|
# the list back before each attempt so a peer's write counts as success.
|
|
- name: Trust the runner workspace
|
|
shell: pwsh
|
|
run: |
|
|
$workspace = $env:GITHUB_WORKSPACE -replace '\\', '/'
|
|
foreach ($attempt in 1..10) {
|
|
$trusted = git config --global --get-all safe.directory
|
|
if ($trusted -contains $workspace) { exit 0 }
|
|
git config --global --add safe.directory $workspace
|
|
if ($LASTEXITCODE -eq 0) { exit 0 }
|
|
Start-Sleep -Milliseconds (200 * $attempt)
|
|
}
|
|
throw "Could not add $workspace to the global safe.directory list"
|
|
|
|
- name: Install Boost
|
|
shell: pwsh
|
|
run: ./scripts/ci/install-boost.ps1
|
|
|
|
# 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 -StagingRoot "$env:RUNNER_TEMP/product-data" -BuildDir build-release
|
|
|
|
# A uiAccess=true binary cannot be launched from the non-interactive
|
|
# service session used by this runner. Test the normal build first, then
|
|
# embed the manifest before upload and signing.
|
|
- name: Embed the Server uiAccess manifest
|
|
shell: pwsh
|
|
run: ./scripts/ci/embed-server-manifest.ps1 -BuildDir server/build-release/bin/Release
|
|
|
|
- name: Verify the embedded Server uiAccess manifest
|
|
shell: pwsh
|
|
run: ./scripts/ci/check-server-binaries.ps1 -BuildDir server/build-release/bin/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
|
|
|
|
- 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@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 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: [self-hosted, Windows, X64, msime-windows]
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
# Build provenance. Unlike Authenticode this costs nothing and needs no certificate, and the
|
|
# Sigstore transparency log it writes to is outside this organisation's control, so it is the
|
|
# one integrity signal a repository admin cannot forge by swapping a release asset.
|
|
id-token: write
|
|
attestations: 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 dictionary release is the only source that still arrives from
|
|
# outside, so it lands beside the components in the one checkout.
|
|
STAGING_ROOT: .
|
|
steps:
|
|
# The helpcodes are collected out of engine/helpcode, which is a directory of this repository,
|
|
# so a plain checkout of the release commit already carries them.
|
|
- name: Check out the release commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.target_sha }}
|
|
persist-credentials: false
|
|
|
|
# Some runner instances leave _work owned by SYSTEM while the job itself
|
|
# runs as `runner`. checkout still succeeds, so the first plain git call
|
|
# is what exits 128 on dubious ownership, minutes into the job.
|
|
#
|
|
# The self-hosted jobs are separate runner instances on one machine, so
|
|
# they share C:\Users\runner\.gitconfig and reach this step in the same
|
|
# second. Whoever loses that race gets "could not lock config file". Read
|
|
# the list back before each attempt so a peer's write counts as success.
|
|
- name: Trust the runner workspace
|
|
shell: pwsh
|
|
run: |
|
|
$workspace = $env:GITHUB_WORKSPACE -replace '\\', '/'
|
|
foreach ($attempt in 1..10) {
|
|
$trusted = git config --global --get-all safe.directory
|
|
if ($trusted -contains $workspace) { exit 0 }
|
|
git config --global --add safe.directory $workspace
|
|
if ($LASTEXITCODE -eq 0) { exit 0 }
|
|
Start-Sleep -Milliseconds (200 * $attempt)
|
|
}
|
|
throw "Could not add $workspace to the global safe.directory list"
|
|
|
|
- name: Verify the release commit is on main
|
|
shell: bash
|
|
run: bash scripts/ci/verify-commit-on-main.sh
|
|
|
|
- name: Download the x64 TSF binary and symbols
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: tsf-x64
|
|
path: windows/build64-release/Release
|
|
|
|
- name: Download the 32-bit TSF binary and symbols
|
|
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
|
|
|
|
# The word-lattice language model is the one product input that is neither built from this
|
|
# repository's sources nor published in the dictionary release: it is converted here from the
|
|
# ARPA corpus pinned in language-model/lock.json. Both the download and the converted result
|
|
# are checked against that lock, so this step is reproducible rather than a fetch of whatever
|
|
# the upstream file server serves today. It configures engine/ngram alone, so it needs no
|
|
# vcpkg and no SQLite on this runner.
|
|
- name: Build the word-lattice language model
|
|
shell: pwsh
|
|
run: ./scripts/build-language-model.ps1
|
|
|
|
# The neural sentence models are downloaded from the chinese-ime-lm release pinned in
|
|
# neural-model/lock.json and checked against the SHA256 recorded there. Prepare-PackageFiles.ps1
|
|
# asserts they exist, so this has to run before the payload is collected.
|
|
- name: Download the neural sentence models
|
|
shell: pwsh
|
|
run: ./scripts/fetch-neural-model.ps1
|
|
|
|
- name: Decide the signing mode
|
|
id: signing
|
|
shell: pwsh
|
|
env:
|
|
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_BASE64 }}
|
|
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERTIFICATE_PASSWORD }}
|
|
CERTIFICATE_THUMBPRINT: ${{ vars.WINDOWS_SIGNING_CERTIFICATE_THUMBPRINT }}
|
|
run: ./scripts/ci/detect-release-signing.ps1
|
|
|
|
# -IncludeSymbols is what makes the published installer carry the PDBs matching its binaries.
|
|
# Staging symbols is opt-in because they are ~140 MB against ~20 MB of binaries and Inno Setup
|
|
# compresses them with solid LZMA2, which is why the local test scripts leave them out.
|
|
- 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 .
|
|
-HelpCodeDirectory engine/helpcode -IncludeSymbols
|
|
|
|
- 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
|
|
|
|
# Every payload EXE/DLL, not just the uiAccess server. The server is the only one that
|
|
# technically requires a signature (uiAccess=true is ignored without one), but Microsoft
|
|
# Defender blocks the unsigned helper processes -- settings page, emoji panel, watchdog --
|
|
# and the user sees an input method that installs and then cannot open its own panels.
|
|
- name: Sign the payload 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 }}
|
|
CERTIFICATE_THUMBPRINT: ${{ vars.WINDOWS_SIGNING_CERTIFICATE_THUMBPRINT }}
|
|
TIMESTAMP_URL: ${{ vars.WINDOWS_SIGNING_TIMESTAMP_URL || 'http://timestamp.digicert.com' }}
|
|
run: ../scripts/ci/sign-binaries.ps1 -Recurse -Path 'server_exe', 'tsf_dll'
|
|
|
|
- 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 }}
|
|
CERTIFICATE_THUMBPRINT: ${{ vars.WINDOWS_SIGNING_CERTIFICATE_THUMBPRINT }}
|
|
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: Attest the installer's build provenance
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-path: ${{ steps.asset.outputs.asset_path }}
|
|
|
|
- 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
|
|
RELEASE_TRIGGER: ${{ github.event_name }}
|
|
TARGET_SHA: ${{ needs.prepare.outputs.target_sha }}
|
|
run: bash scripts/ci/publish-release.sh
|
|
|
|
# msime.app/update.json is what the settings page's "检查更新" reads, and MSIME-Web only
|
|
# regenerated it from a */30 cron. Scheduled events on public repositories are delayed heavily
|
|
# -- measured runs sat roughly two hours apart -- so the site advertised an old version for
|
|
# hours after every release. MSIME-Web already declares a repository_dispatch trigger for
|
|
# exactly this and nothing was ever sending it.
|
|
#
|
|
# This runs after publishing on purpose. If the dispatch fails the release is already out and
|
|
# correct; what the failure means is that the website is now stale and someone has to act, so
|
|
# it is reported as a failure rather than swallowed.
|
|
#
|
|
# No event condition: the enclosing job already only runs when a release is being published, and gating this on workflow_dispatch limited the notification to the repair path -- the one branch of this workflow that almost never runs. Every release from the normal push path published in silence, MSIME-Web received no dispatch at all, and the site waited for a cron that GitHub delivers roughly seven times a day rather than the forty-eight the schedule asks for. 0.8.0 reached msime.app a day late that way.
|
|
- name: Refresh the download manifest on msime.app
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
|
run: |
|
|
if [[ -z "$GH_TOKEN" ]]; then
|
|
echo '::error::RELEASE_PLEASE_TOKEN is required to notify MSIME-Web; msime.app/update.json will stay on the previous version until its cron catches up.'
|
|
exit 1
|
|
fi
|
|
gh api --method POST repos/metasequoiaime/MSIME-Web/dispatches -f event_type=update-manifest
|