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