Files

381 lines
17 KiB
YAML

name: Dev build
# Development builds for contributors. The point is that a newcomer does not install vcpkg,
# Boost, Inno Setup and the Windows SDK to try a change; the runner builds, packages and signs,
# and the artifact is a full installer with a fresh self-signed certificate plus the public key
# to trust it.
#
# Three ways in. A feature-branch push builds automatically, so the artifact is ready when the
# push finishes. A pull_request run gives reviewers and fork contributors an installer for real
# device testing -- it uses pull_request, not pull_request_target, deliberately: building the
# PR's code runs scripts from that code, and pull_request_target would hand those scripts a
# writable token. workflow_dispatch stays for picking a single component or rebuilding without
# a push. develop and main never trigger this; they have ci.yml and release.yml.
#
# This is a test channel, not a release: the version always carries -dev.<date>.<hash>, the
# certificate is minted per run, and nothing here creates a release or touches the update
# manifest. ci.yml remains the merge gate; this workflow deliberately skips the suite the CI
# already runs and only builds what an installable artifact needs.
on:
workflow_dispatch:
inputs:
component:
description: Which component to build (all = full signed installer)
required: true
type: choice
default: all
options:
- all
- tip
- server
push:
branches-ignore:
- develop
- main
- 'release-please--*'
# The Windows 7 port lives in its own repository with dev-build-win7.yml; branches named for
# it are pushed here from that work and would only rebuild an artifact nobody asked for.
- 'win7*'
tags-ignore:
- '**'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- develop
paths-ignore:
- '**.md'
- 'docs/**'
permissions:
contents: read
# push and pull_request have no inputs; both build everything, since those events exist to
# produce an installable artifact and a component filter nobody can set would only mislead.
# Artifacts carry a working installer plus its signing certificate. The default 90-day retention
# would pile up hundreds of megabytes per push on an active branch, so every upload below passes
# retention-days: 7 -- enough to notice a build and install it, and the next push replaces it.
env:
DEV_ARTIFACT_RETENTION: 7
# Same ref, newer run wins; an older run keeps burning a runner for nothing.
concurrency:
group: dev-build-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tip:
name: TSF ${{ matrix.name }}
if: ${{ (inputs.component || 'all') == 'all' || (inputs.component || 'all') == 'tip' }}
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 source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Stamp the dev version into the resource
shell: pwsh
run: |
# YYMMDD in the fourth VERSIONINFO slot: the quad must stay four 16-bit fields, and a
# date-compressed value sorts a dev DLL above its own base and below the next release.
$base = python scripts/dev-build-version.py --base
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$date = python scripts/dev-build-version.py --date
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python scripts/apply_version.py --version "$base.$date"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- 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 ${{ 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 Release binary and symbols
shell: pwsh
run: ./scripts/ci/check-tsf-dll.ps1 -BuildDir '${{ matrix.build_dir }}'
- name: Upload the TSF binary and symbols
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dev-tsf-${{ matrix.name }}
retention-days: ${{ env.DEV_ARTIFACT_RETENTION }}
# Same layout convention as the release workflow: download-artifact restores the
# directory contents where Prepare-PackageFiles.ps1 expects them.
path: ${{ matrix.build_dir }}/Release
if-no-files-found: error
server:
name: Server
if: ${{ (inputs.component || 'all') == 'all' || (inputs.component || 'all') == 'server' }}
runs-on: windows-2025
timeout-minutes: 90
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: Stamp the dev version into the resource
# The TSF DLL carries the product's VERSIONINFO even in a server-only build, because the
# resource is stamped before configure; keep every build of this run on one version.
shell: pwsh
run: |
$base = python scripts/dev-build-version.py --base
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$date = python scripts/dev-build-version.py --date
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python scripts/apply_version.py --version "$base.$date"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Restore dependency binaries
id: vcpkg-cache
uses: ./.github/actions/vcpkg-cache
with:
scope: server-x64-windows-and-static-md
- 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
- 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
# Ninja Multi-Config so sccache plugs in through CMAKE_<LANG>_COMPILER_LAUNCHER, mirroring
# ci.yml's Server job; the bin/Release layout is what every later step reads. The override
# embeds the full dev version string into the native settings page; server/CMakeLists.txt
# accepts a semver prerelease suffix, and the about page's ParseVersion strips it for the
# update check while displaying it verbatim.
- name: Configure
shell: pwsh
run: |
$display = python scripts/dev-build-version.py --display
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$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',
"-DMSIME_PRODUCT_VERSION_OVERRIDE=$display"
)
$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: 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
# uiAccess=true is ignored without a signature, so the manifest goes in before upload and
# the whole payload gets signed in the package job.
- name: Embed the Server uiAccess manifest
shell: pwsh
run: ./scripts/ci/embed-server-manifest.ps1 -BuildDir server/build-release/bin/Release
# component=server uploads this directory as an installable artifact with no package job to
# sign it. Windows ignores uiAccess=true without a trusted signature, so the binary would
# install and then silently lose the privilege. component=all leaves it unsigned on purpose:
# Prepare-PackageFiles.ps1 copies these files into the payload and sign-dev-build.ps1 signs
# the copies, so signing here would only add a second signing pass over the same bytes.
- name: Sign the Server build for the standalone artifact
if: ${{ (inputs.component || 'all') == 'server' }}
shell: pwsh
env:
DEV_CERT_PATH: ${{ runner.temp }}/dev-server-certificate.cer
run: |
# -Include 只有在 -Path 带通配符或同时给 -Recurse 时才生效,这里显式计算再交给参数,
# 免得把一条空列表传进脚本后它才在内部报错。
$targets = @(
Get-ChildItem -LiteralPath 'server/build-release/bin/Release' -Recurse -File |
Where-Object { $_.Extension -in @('.exe', '.dll') } |
ForEach-Object FullName
)
if ($targets.Count -eq 0) { throw 'No EXE or DLL found to sign under server/build-release/bin/Release.' }
./installer/sign-dev-build.ps1 -FilePath $targets
- name: Upload the Server build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dev-server
retention-days: ${{ env.DEV_ARTIFACT_RETENTION }}
path: server/build-release/bin/Release
if-no-files-found: error
# The certificate has to travel with the standalone Server artifact too, or nobody can import
# the trust that the signature above depends on.
- name: Upload the signing certificate
if: ${{ (inputs.component || 'all') == 'server' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dev-server-certificate
retention-days: ${{ env.DEV_ARTIFACT_RETENTION }}
path: ${{ runner.temp }}/dev-server-certificate.cer
if-no-files-found: error
package:
name: Package installer
needs: [tip, server]
# Only component=all produces an installer: packaging needs both TSF architectures and the
# Server, so there is no meaningful way to package a single-component run.
if: ${{ !cancelled() && needs.tip.result == 'success' && needs.server.result == 'success' && (inputs.component || 'all') == 'all' }}
runs-on: windows-2025
timeout-minutes: 60
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Read the dev version
id: version
shell: pwsh
run: |
$display = python scripts/dev-build-version.py --display
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
"display=$display" >> $env:GITHUB_OUTPUT
"installer=installer/Output/MetasequoiaIME_Setup_v$display.exe" >> $env:GITHUB_OUTPUT
- name: Download the x64 TSF binary and symbols
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dev-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: dev-tsf-Win32
path: windows/build32-release/Release
- name: Download the Server build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dev-server
path: server/build-release/bin/Release
- name: Download the dictionaries
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAGING_ROOT: .
run: bash scripts/ci/download-dictionaries.sh
- name: Build the word-lattice language model
shell: pwsh
run: ./scripts/build-language-model.ps1
- name: Download the neural sentence models
shell: pwsh
run: ./scripts/fetch-neural-model.ps1
- name: Collect the installer payload
shell: pwsh
working-directory: installer
env:
TARGET_VERSION: ${{ steps.version.outputs.display }}
run: >
pwsh -File ./Prepare-PackageFiles.ps1 -TargetVersion $env:TARGET_VERSION
-RepoRoot (Resolve-Path '..').Path
-TsfDirectory windows -ServerDirectory server -NoticesDirectory .
-HelpCodeDirectory engine/helpcode
- name: Sign the payload binaries
shell: pwsh
working-directory: installer
env:
DEV_CERT_PATH: ${{ runner.temp }}/dev-build.cer
run: ./sign-dev-build.ps1
- 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
shell: pwsh
working-directory: installer
env:
DEV_CERT_PATH: ${{ runner.temp }}/dev-build.cer
run: ./sign-dev-build.ps1 -Installer
- name: Name the artifact and record its checksum
id: asset
shell: pwsh
working-directory: installer
env:
TARGET_VERSION: ${{ steps.version.outputs.display }}
run: |
$built = "Output/MetasequoiaIME_Setup_v$env:TARGET_VERSION.exe"
if (-not (Test-Path $built)) { throw "Inno Setup did not produce $built" }
$hash = (Get-FileHash $built -Algorithm SHA256).Hash.ToLower()
$size = [math]::Round((Get-Item $built).Length / 1MB, 1)
Write-Host "$built ($size MB, sha256 $hash)"
"installer_path=$((Resolve-Path $built).Path)" >> $env:GITHUB_OUTPUT
"installer_sha256=$hash" >> $env:GITHUB_OUTPUT
- name: Upload the installer and the signing certificate
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dev-installer
retention-days: ${{ env.DEV_ARTIFACT_RETENTION }}
path: |
${{ steps.asset.outputs.installer_path }}
${{ runner.temp }}/dev-build.cer
installer/import-dev-trust.ps1
if-no-files-found: error
- name: Summarise the build
shell: pwsh
env:
TARGET_VERSION: ${{ steps.version.outputs.display }}
INSTALLER_SHA256: ${{ steps.asset.outputs.installer_sha256 }}
run: |
@(
'### Dev build'
''
"| Field | Value |"
"| --- | --- |"
"| Version | $env:TARGET_VERSION |"
"| Installer SHA256 | ``$env:INSTALLER_SHA256`` |"
''
'To install: download the artifact, unzip, then in an **admin** PowerShell run'
'`import-dev-trust.ps1` and launch the installer. Re-run the trust import for every'
'new build; the certificate is minted per run and the script replaces the old one.'
) >> $env:GITHUB_STEP_SUMMARY