Files
msime-windows/installer/Prepare-PackageFiles.ps1
tokisaki galaxy d376b93e03 installer: let the packaging carry a prerelease version string
Two independent obstacles surfaced the first time a version with a hyphen
reached the installer:

- Inno Setup's VersionInfoVersion only accepts digits and dots, so ISCC
  aborted on 0.7.1-dev.20261019.<hash>. AppVersion, the output filename and
  the versioned DLL directory are free text and keep the full string; only
  VersionInfoVersion moves to a separate MyVersionInfo define that
  Prepare-PackageFiles.ps1 fills with the part before the hyphen, failing
  loudly if what remains is not numeric. Releases pass a bare semver, strip
  nothing, and behave exactly as before.
- TsfRoot64 called ApplyPathRedirRulesForCurrentProcess, an Inno Setup 7
  support function the windows-2025 runner's 6.7.1 does not have. #925 added
  it five hours after the last hosted-compiler run, and every release since
  then compiled on the self-hosted Inno 7, so the latent break never fired;
  the dev-build's hosted package job is the first to execute that path. The
  call was a no-op anyway: with
  ArchitecturesInstallIn64BitMode=x64compatible the setup process is 64-bit
  on every supported OS, so {sys} already expands to the real System32 and
  no WOW64 redirection applies. Expand the constant directly, with the #434
  warning in this README updated to name the function explicitly.
2026-10-19 16:01:05 +08:00

375 lines
23 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[CmdletBinding()]
param(
[string]$TargetVersion = '0.0.1',
[string]$RepoRoot = (Split-Path -Parent $PSScriptRoot),
# Component paths are relative to RepoRoot and default to the consolidated layout.
# Historical or custom layouts remain available through explicit overrides.
[string]$TsfDirectory = 'windows',
[string]$ServerDirectory = 'server',
[string]$HelpCodeDirectory = 'engine/helpcode',
# U 模式的部件拆字反查表(decomposition.txt)随包发布到数据目录的 radical\ 下。
[string]$RadicalDirectory = 'engine/radical',
# Google 解码器的系统词典。仓库里有两份完全相同的副本:这一份是 engine/contracts/assets
# 的清单声明的来源,server/assets/tables 下那份是跑测试时拷进数据目录的开发副本。打包
# 取清单声明的那份,免得哪天两份不同步了装出来的和测出来的不是一个东西。
[string]$PinyinModelDirectory = 'engine/googlepinyinime-rev/data',
[string]$DictionaryDirectory = 'MetasequoiaImeDict',
# 词格打分用的语言模型不随词库发布下发,而是由 scripts/build-language-model.ps1
# 从钉住的上游语料现场转换到这个目录。
[string]$LanguageModelDirectory = 'language-model',
# 神经整句速度档模型(chinese-ime-lm 的 safetensors)从这个目录下发,与 sc.lm 同为资源根下的
# 只读文件。缺文件时引擎静默不出神经整句,但打包仍然必须带上,故这里断言存在。效果档模型
# 不随包分发,由设置页按需下载到 <数据目录>\models\neural-desktop\。
[string]$NeuralModelDirectory = 'neural-model',
# THIRD_PARTY_NOTICES.txt used to sit next to the tip's sources. In the consolidated repository
# the notice covers the whole product and lives at the root, one level above windows/, so where
# to read it is no longer answered by where the tip is.
[string]$NoticesDirectory = '.',
[switch]$Light,
# PDBs are ~140 MB against ~20 MB of actual binaries, and Inno Setup compresses them with solid
# LZMA2, so staging them is what makes a local packaging run take minutes. Symbols are therefore
# opt-in: the release job and test-symbols.ps1 pass this, everyday local runs do not. The
# assertions above still require the build to have produced matching PDBs either way.
[switch]$IncludeSymbols
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
function Assert-PathExists {
param([Parameter(Mandatory)][string]$LiteralPath, [Parameter(Mandatory)][string]$Description)
if (-not (Test-Path -LiteralPath $LiteralPath)) {
throw "$Description 不存在:$LiteralPath"
}
}
function Copy-DirectoryContents {
param([Parameter(Mandatory)][string]$Source, [Parameter(Mandatory)][string]$Destination)
Assert-PathExists -LiteralPath $Source -Description '源目录'
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
Get-ChildItem -LiteralPath $Source -Force | Copy-Item -Destination $Destination -Recurse -Force
}
function Reset-Directory {
param([Parameter(Mandatory)][string]$LiteralPath)
if (Test-Path -LiteralPath $LiteralPath) {
Remove-Item -LiteralPath $LiteralPath -Recurse -Force
}
New-Item -ItemType Directory -Path $LiteralPath -Force | Out-Null
}
$serverRelease = Join-Path $RepoRoot (Join-Path $ServerDirectory 'build-release\bin\Release')
$dictionaryReplayRelease = Join-Path $serverRelease 'MetasequoiaImeDictionaryReplay.exe'
$tsf32Release = Join-Path $RepoRoot (Join-Path $TsfDirectory 'build32-release\Release\MetasequoiaImeTsf.dll')
$tsf64Release = Join-Path $RepoRoot (Join-Path $TsfDirectory 'build64-release\Release\MetasequoiaImeTsf.dll')
$tsf32Pdb = Join-Path $RepoRoot (Join-Path $TsfDirectory 'build32-release\Release\MetasequoiaImeTsf.pdb')
$tsf64Pdb = Join-Path $RepoRoot (Join-Path $TsfDirectory 'build64-release\Release\MetasequoiaImeTsf.pdb')
# 原生(Direct2D)设置窗口用到的图标与图片,按原相对路径装到数据目录的 assets\settings\。
$settingsAssets = Join-Path $RepoRoot (Join-Path $ServerDirectory 'assets\settings')
$defaultSkinsSource = Join-Path $RepoRoot 'skins\default'
$bundledSkinsSource = Join-Path $RepoRoot 'skins'
$serverConfig = Join-Path $RepoRoot (Join-Path $ServerDirectory 'assets\config\config.toml')
$factoryConfig = Join-Path $PSScriptRoot 'default_config\config.default.toml'
$pinyinTable = Join-Path $RepoRoot (Join-Path $ServerDirectory 'assets\tables\pinyin.txt')
$pinyinModel = Join-Path $RepoRoot (Join-Path $PinyinModelDirectory 'dict_pinyin.dat')
$helpcodeSource = Join-Path $RepoRoot (Join-Path $HelpCodeDirectory 'helpcodes')
$radicalTable = Join-Path $RepoRoot (Join-Path $RadicalDirectory 'decomposition.txt')
$appIcon = Join-Path $RepoRoot (Join-Path $ServerDirectory 'src\resource\MetasequoiaIME.ico')
# 输入法语言配置的图标,与 TSF DLL 里的 IDIS_METASEQUOIAIME 是同一个文件。安装器把它放到固定路径,
# DLL 注册时优先用它,免得图标路径随版本目录变化(见 msime_setup.iss)。
$tipIcon = Join-Path $RepoRoot (Join-Path $TsfDirectory 'image\msime.ico')
$thirdPartyNotices = Join-Path $RepoRoot (Join-Path $NoticesDirectory 'THIRD_PARTY_NOTICES.txt')
$license = Join-Path $RepoRoot 'LICENSE'
$dictionaryDb = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'out\msime.db')
$dictionaryManifest = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'out\dictionary-manifest.json')
$japaneseModel = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'out\dict_japanese.dat')
$japaneseModelLicense = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'source\mozc_dictionary_oss\README.txt')
$englishDb = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'out\english.db')
$othersDb = Join-Path $RepoRoot (Join-Path $DictionaryDirectory 'out\others.db')
$languageModel = Join-Path $RepoRoot (Join-Path $LanguageModelDirectory 'sc.lm')
$languageModelNotice = Join-Path $RepoRoot (Join-Path $LanguageModelDirectory 'NOTICE.md')
$neuralModelKeyboard = Join-Path $RepoRoot (Join-Path $NeuralModelDirectory 'sentence-model.safetensors')
$neuralModelNotice = Join-Path $RepoRoot (Join-Path $NeuralModelDirectory 'NOTICE.md')
Assert-PathExists -LiteralPath $RepoRoot -Description '源码仓库根目录'
Assert-PathExists -LiteralPath $serverRelease -Description 'Server Release 输出目录'
Assert-PathExists -LiteralPath $dictionaryReplayRelease -Description '用户词库回放程序 Release EXE'
Assert-PathExists -LiteralPath $tsf32Release -Description '32 位 TSF Release DLL'
Assert-PathExists -LiteralPath $tsf64Release -Description '64 位 TSF Release DLL'
Assert-PathExists -LiteralPath $tsf32Pdb -Description '32 位 TSF Release PDB'
Assert-PathExists -LiteralPath $tsf64Pdb -Description '64 位 TSF Release PDB'
$serverExecutables = @(
Get-ChildItem -LiteralPath $serverRelease -Recurse -File -Filter '*.exe' |
Where-Object { $_.BaseName -notlike '*Tests' -and $_.BaseName -notlike 'test_*' }
)
$missingServerPdb = @(
$serverExecutables |
Where-Object {
-not (Test-Path -LiteralPath (Join-Path $_.DirectoryName "$($_.BaseName).pdb"))
} |
ForEach-Object { Join-Path $_.DirectoryName "$($_.BaseName).pdb" }
)
if ($missingServerPdb.Count -gt 0) {
throw "Server Release 缺少同名 PDB:$($missingServerPdb -join ', ')"
}
Assert-PathExists -LiteralPath $serverConfig -Description 'Server config.toml'
Assert-PathExists -LiteralPath $appIcon -Description '应用图标'
Assert-PathExists -LiteralPath $tipIcon -Description '输入法语言配置图标 msime.ico'
Assert-PathExists -LiteralPath $thirdPartyNotices -Description '第三方声明 THIRD_PARTY_NOTICES.txt'
Assert-PathExists -LiteralPath $license -Description '许可证 LICENSE'
Assert-PathExists -LiteralPath $settingsAssets -Description '设置窗口图片资源目录'
Assert-PathExists -LiteralPath $defaultSkinsSource -Description '内置皮肤设置清单 skins\default'
if (-not $Light) {
Assert-PathExists -LiteralPath $factoryConfig -Description '出厂配置 default_config\config.default.toml'
Assert-PathExists -LiteralPath $pinyinTable -Description '完整拼音音节表 pinyin.txt'
Assert-PathExists -LiteralPath $pinyinModel -Description 'Google 解码器系统词典 dict_pinyin.dat'
Assert-PathExists -LiteralPath $helpcodeSource -Description '辅助码目录'
Assert-PathExists -LiteralPath $radicalTable -Description 'U 模式部件拆字表 decomposition.txt'
Assert-PathExists -LiteralPath $dictionaryDb -Description '词库数据库 msime.db'
Assert-PathExists -LiteralPath $japaneseModel -Description '日语整句模型 dict_japanese.dat'
Assert-PathExists -LiteralPath $japaneseModelLicense -Description 'Mozc 日语词典授权声明'
Assert-PathExists -LiteralPath $englishDb -Description '英文词库数据库 english.db'
python -c @"
import sqlite3, sys
cols = list(sqlite3.connect(sys.argv[1]).execute('PRAGMA table_info(english_words)'))
names = {row[1] for row in cols}
pk = [row[1] for row in cols if row[5] > 0]
if 'weight' not in names or pk != ['word', 'display']:
raise SystemExit('english.db schema is stale; rebuild with weight and PRIMARY KEY(word, display)')
"@ $englishDb
if ($LASTEXITCODE -ne 0) {
throw "英文词库数据库 schema 检查失败:$englishDb"
}
Assert-PathExists -LiteralPath $othersDb -Description '杂项数据库 others.db'
# 摘要校验留在 build-language-model.ps1 里:它既有 lock.json 又刚生成完文件。
# 这里只确认文件在,与 msime.db 一样 —— 词库的摘要也是由 product_lock.py 校验的。
Assert-PathExists -LiteralPath $languageModel `
-Description '词格整句语言模型 sc.lm(用 scripts\build-language-model.ps1 生成)'
Assert-PathExists -LiteralPath $languageModelNotice -Description 'libime 语言模型授权声明 NOTICE.md'
Assert-PathExists -LiteralPath $neuralModelKeyboard -Description '神经整句模型 sentence-model.safetensors'
Assert-PathExists -LiteralPath $neuralModelNotice -Description 'chinese-ime-lm 授权声明 NOTICE.md'
}
$targetAppData = Join-Path $PSScriptRoot 'app_data'
$targetServer = Join-Path $PSScriptRoot 'server_exe'
$targetTsf = Join-Path $PSScriptRoot 'tsf_dll'
if ($Light) {
Write-Host '轻量模式:跳过词库、辅助码、拼音表和出厂配置,只刷新 TSF、Server 和设置窗口图片资源。'
New-Item -ItemType Directory -Path $targetAppData -Force | Out-Null
}
else {
Reset-Directory -LiteralPath $targetAppData
if (-not (Get-Content -LiteralPath $pinyinTable | Where-Object { $_.Trim() -eq 'xing' })) {
throw "完整拼音音节表缺少 xing:$pinyinTable"
}
Copy-Item -LiteralPath $pinyinTable -Destination (Join-Path $targetAppData 'pinyin.txt') -Force
# Google 解码器的整句候选(CandidateSource::Fallback)没有这份词典就完全出不来,而且和
# sc.lm 一样是静默失败:im_open_decoder 返回 false,解码器直接不出候选,不报任何错。
# 与它配对的 user_dict.dat 是 role=user 的可写文件,profiles 为空,故意不进包 ——
# 引擎首次使用时由 UserDict::reset 在用户数据目录下自行建出来。
Copy-Item -LiteralPath $pinyinModel -Destination (Join-Path $targetAppData 'dict_pinyin.dat') -Force
Copy-Item -LiteralPath $dictionaryDb -Destination (Join-Path $targetAppData 'msime.db') -Force
if (Test-Path -LiteralPath $dictionaryManifest) {
Copy-Item -LiteralPath $dictionaryManifest -Destination (Join-Path $targetAppData 'dictionary-manifest.json') -Force
}
Copy-Item -LiteralPath $japaneseModel -Destination (Join-Path $targetAppData 'dict_japanese.dat') -Force
Copy-Item -LiteralPath $japaneseModelLicense -Destination (Join-Path $targetAppData 'MOZC_DICTIONARY_LICENSE.txt') -Force
Copy-Item -LiteralPath $englishDb -Destination (Join-Path $targetAppData 'english.db') -Force
Copy-Item -LiteralPath $othersDb -Destination (Join-Path $targetAppData 'others.db') -Force
# sc.lm 落在资源目录根下,与 msime.db 同级:engine/contracts/assets 的清单就是这么
# 声明这个资源的路径的,引擎按该清单里的文件名去找。
Copy-Item -LiteralPath $languageModel -Destination (Join-Path $targetAppData 'sc.lm') -Force
# 模型数据是 LGPL-2.1-or-later 的第三方作品,声明必须跟着二进制到用户磁盘上,
# 理由同下面 rime-ice 那段。
Copy-Item -LiteralPath $languageModelNotice -Destination (Join-Path $targetAppData 'libime-lm-NOTICE.md') -Force
# 神经整句速度档模型与 sc.lm 一样落在资源目录根下,文件名由 engine/contracts/assets 清单声明。
# 缺文件时引擎静默不出神经整句候选(shared_sentence_model 返回 nullptr),不影响其它候选。
Copy-Item -LiteralPath $neuralModelKeyboard -Destination (Join-Path $targetAppData 'sentence-model.safetensors') -Force
# 权重是 Apache-2.0 的第三方作品,授权声明必须随二进制到用户磁盘上。
Copy-Item -LiteralPath $neuralModelNotice -Destination (Join-Path $targetAppData 'chinese-ime-lm-NOTICE.md') -Force
$defaultConfigPath = Join-Path $targetAppData 'config.default.toml'
# 出厂配置来自本仓库的 default_config,不依赖本机是否已安装输入法。
# 安装脚本用 onlyifdoesntexist 生成用户 config.toml,升级不会覆盖已有方案/主题。
Copy-Item -LiteralPath $factoryConfig -Destination $defaultConfigPath -Force
$defaultConfig = Get-Content -LiteralPath $defaultConfigPath -Raw
if ($defaultConfig -notmatch '(?m)^schema\s*=\s*"quanpin"\s*$') {
throw '出厂配置的 input.schema 必须是 quanpin。'
}
if ($defaultConfig -notmatch '(?m)^theme_mode\s*=\s*"system"\s*$') {
throw '出厂配置的 appearance.theme_mode 必须是 system。'
}
if ($defaultConfig -match '(?m)^diagnostic_log\s*=\s*true\s*$') {
throw '出厂配置不应默认打开 diagnostic_log。'
}
if ($defaultConfig -notmatch '(?m)^settings_window_linger\s*=\s*"off"\s*$') {
throw '出厂配置的 appearance.settings_window_linger 必须是 off(关闭后立即退出)。'
}
foreach ($sentenceSwitch in @('sentence_wordlattice', 'sentence_google', 'sentence_neural_keyboard')) {
if ($defaultConfig -notmatch "(?m)^$sentenceSwitch\s*=\s*true\s*$") {
throw "出厂配置的 association.$sentenceSwitch 必须默认开启。"
}
}
$defaultConfig = $defaultConfig.TrimEnd("`r", "`n") + "`r`n"
Set-Content -LiteralPath $defaultConfigPath -Value $defaultConfig -Encoding utf8NoBOM -NoNewline
foreach ($stagedUserConfig in @('config.toml', 'config.base.toml')) {
$stagedPath = Join-Path $targetAppData $stagedUserConfig
if (Test-Path -LiteralPath $stagedPath) {
Remove-Item -LiteralPath $stagedPath -Force
}
}
$targetHelpcodes = Join-Path $targetAppData 'helpcodes'
Reset-Directory -LiteralPath $targetHelpcodes
Copy-DirectoryContents -Source $helpcodeSource -Destination $targetHelpcodes
# U 模式的拆字表落在数据目录的 radical\ 下,路径由 engine/contracts/assets 清单声明。
$targetRadical = Join-Path $targetAppData 'radical'
Reset-Directory -LiteralPath $targetRadical
Copy-Item -LiteralPath $radicalTable -Destination (Join-Path $targetRadical 'decomposition.txt') -Force
}
# 轻量模式不清空 app_data,旧版本留下的 html\(WebView2 页面)要在这里删掉,免得被打进包里。
$legacyHtml = Join-Path $targetAppData 'html'
if (Test-Path -LiteralPath $legacyHtml) {
Remove-Item -LiteralPath $legacyHtml -Recurse -Force
}
$targetAssets = Join-Path $targetAppData 'assets'
$targetSettingsAssets = Join-Path $targetAssets 'settings'
if (Test-Path -LiteralPath $targetAssets) {
Remove-Item -LiteralPath $targetAssets -Recurse -Force
}
Copy-DirectoryContents -Source $settingsAssets -Destination $targetSettingsAssets
# 内置皮肤的出厂清单(开关与完整外观,含 canvas 基底)。安装脚本每次都整份覆盖到数据目录的
# skins\default,用户在那里的改动升级后不保留。
# 不放进 app_data:app_data 的顶层条目都在卸载时按 IsShippedAppDataItem 删除,而 skins
# 整个目录归用户(外部皮肤也在里面),不能进那份名单。
$targetDefaultSkins = Join-Path $PSScriptRoot 'default_skins'
Reset-Directory -LiteralPath $targetDefaultSkins
Copy-DirectoryContents -Source $defaultSkinsSource -Destination $targetDefaultSkins
# 仓库 skins\ 下的外部皮肤随包装到数据目录的 skins\<id>,同名的直接覆盖。覆盖前安装程序拿
# bundled_skins.manifest 核对用户机器上的同名目录:内容与随包的一模一样就不动,否则先改名成
# <id>.bak 备份(Server 扫描时跳过 .bak 目录)。清单每行是「id|相对路径|sha256」,
# 安装程序按字节比较,所以路径限定 ASCII、不能含 |。
# 素材授权没核实的皮肤([license] assets = "UNVERIFIED...")只能放在仓库里演示,不进安装包。
$targetBundledSkins = Join-Path $PSScriptRoot 'bundled_skins'
$bundledSkinsManifest = Join-Path $PSScriptRoot 'bundled_skins.manifest'
Reset-Directory -LiteralPath $targetBundledSkins
$manifestLines = [Collections.Generic.List[string]]::new()
$bundledSkinIds = [Collections.Generic.List[string]]::new()
foreach ($skinDir in @(Get-ChildItem -LiteralPath $bundledSkinsSource -Directory | Sort-Object Name)) {
$id = $skinDir.Name
if ($id -ceq 'default') { continue }
if ($id -cnotmatch '^[a-z0-9][a-z0-9._-]{0,63}$' -or $id -match '\.bak$') {
throw "皮肤目录名不是有效的外部皮肤 ID:$($skinDir.FullName)"
}
$skinManifest = Join-Path $skinDir.FullName 'skin.toml'
Assert-PathExists -LiteralPath $skinManifest -Description "皮肤 $id 的 skin.toml"
$skinText = [IO.File]::ReadAllText($skinManifest)
if ($skinText -notmatch "(?m)^\s*id\s*=\s*`"$([regex]::Escape($id))`"\s*(#.*)?$") {
throw "皮肤 $id 的 skin.toml 里 id 与目录名不一致,Server 不会加载它:$skinManifest"
}
if ($skinText -match '(?m)^\s*assets\s*=\s*"UNVERIFIED') {
Write-Host "跳过皮肤 ${id}:素材授权未核实,不随安装包分发。"
continue
}
$destination = Join-Path $targetBundledSkins $id
Copy-DirectoryContents -Source $skinDir.FullName -Destination $destination
foreach ($file in @(Get-ChildItem -LiteralPath $destination -Recurse -File -Force | Sort-Object FullName)) {
$relative = $file.FullName.Substring($destination.Length + 1)
if ($relative -notmatch '^[\x20-\x7E]+$' -or $relative.Contains('|')) {
throw "皮肤文件名只能用 ASCII 且不能含 |(安装程序按清单逐字节比对):$($file.FullName)"
}
$hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
$manifestLines.Add("$id|$relative|$hash")
}
$bundledSkinIds.Add($id)
}
if ($bundledSkinIds.Count -eq 0) {
throw "没有可随包分发的外部皮肤:$bundledSkinsSource"
}
[IO.File]::WriteAllLines($bundledSkinsManifest, $manifestLines, [Text.UTF8Encoding]::new($false))
Write-Host "随包皮肤:$($bundledSkinIds -join ', ')"
# Server Release 输出整体复制,但测试程序及其 PDB 绝不能进入安装包。
# 带 -IncludeSymbols 时其他 PDB 保留在对应 EXE 旁边,方便安装后直接进行崩溃分析。
Reset-Directory -LiteralPath $targetServer
Copy-DirectoryContents -Source $serverRelease -Destination $targetServer
Get-ChildItem -LiteralPath $targetServer -Recurse -File -Filter '*Tests.exe' |
Remove-Item -Force
Get-ChildItem -LiteralPath $targetServer -Recurse -File -Filter 'test_*.exe' |
Remove-Item -Force
if ($IncludeSymbols) {
Get-ChildItem -LiteralPath $targetServer -Recurse -File -Filter '*Tests.pdb' |
Remove-Item -Force
Get-ChildItem -LiteralPath $targetServer -Recurse -File -Filter 'test_*.pdb' |
Remove-Item -Force
}
else {
Get-ChildItem -LiteralPath $targetServer -Recurse -File -Filter '*.pdb' |
Remove-Item -Force
}
Reset-Directory -LiteralPath $targetTsf
$targetTsf32 = Join-Path $targetTsf '32'
$targetTsf64 = Join-Path $targetTsf '64'
New-Item -ItemType Directory -Path $targetTsf32, $targetTsf64 -Force | Out-Null
Copy-Item -LiteralPath $tsf32Release -Destination $targetTsf32 -Force
Copy-Item -LiteralPath $tsf64Release -Destination $targetTsf64 -Force
if ($IncludeSymbols) {
Copy-Item -LiteralPath $tsf32Pdb -Destination $targetTsf32 -Force
Copy-Item -LiteralPath $tsf64Pdb -Destination $targetTsf64 -Force
}
Copy-Item -LiteralPath $appIcon -Destination (Join-Path $PSScriptRoot 'MetasequoiaIME.ico') -Force
Copy-Item -LiteralPath $tipIcon -Destination (Join-Path $PSScriptRoot 'MetasequoiaImeTip.ico') -Force
# rime-ice is GPL-3.0 and requires attribution, and its content forms the bulk of msime.db, so the
# notice has to reach the user's disk rather than only exist in the source repository.
Copy-Item -LiteralPath $thirdPartyNotices -Destination (Join-Path $PSScriptRoot 'THIRD_PARTY_NOTICES.txt') -Force
# GPLv3 sections 4 and 6 require a copy of the licence to reach whoever receives the program, and the
# packaged product includes third-party GPL-3.0 dictionary data. macOS and Linux already install the
# licence text (CMakeLists.txt in MSIME-Apple and MSIME-Linux); Windows is the platform that actually
# ships at volume and was the only one omitting it. THIRD_PARTY_NOTICES.txt does not cover this: it
# points at "the LICENSE file" without carrying the GPL text itself.
Copy-Item -LiteralPath $license -Destination (Join-Path $PSScriptRoot 'LICENSE.txt') -Force
$targetIss = Join-Path $PSScriptRoot 'msime_setup.iss'
Assert-PathExists -LiteralPath $targetIss -Description '安装脚本'
$issContent = Get-Content -LiteralPath $targetIss -Raw
if ($issContent -notmatch '(?m)^#define\s+MyAppVersion\s+"[^"]+"\s*$') {
throw '未能在安装脚本中找到 MyAppVersion。'
}
$updatedIss = [regex]::Replace(
$issContent,
'(?m)^#define\s+MyAppVersion\s+"[^"]+"\s*$',
"#define MyAppVersion `"$TargetVersion`""
)
# VersionInfoVersion 只接受数字和点号;从目标版本剥掉 -后缀(dev 构建的
# 0.7.1-dev.20261019.<hash> → 0.7.1),正式版本剥了也原样。
$versionInfo = ($TargetVersion -replace '-.*$', '')
if ($versionInfo -notmatch '^[0-9]+(\.[0-9]+)*$') {
throw "目标版本剥掉后缀后不是数字版本号,无法写进 VersionInfoVersion:$TargetVersion"
}
$updatedIss = [regex]::Replace(
$updatedIss,
'(?m)^#define\s+MyVersionInfo\s+"[^"]+"\s*$',
"#define MyVersionInfo `"$versionInfo`""
)
# .gitattributes pins this repo to eol=lf; emit LF so packaging does not leave the file as a
# CRLF-only diff that git flags as modified while `git diff` shows no textual change.
$updatedIss = $updatedIss.TrimEnd("`r", "`n") + "`n"
Set-Content -LiteralPath $targetIss -Value $updatedIss -Encoding utf8NoBOM -NoNewline
$serverBinaryCount = @(Get-ChildItem -LiteralPath $targetServer -Recurse -File -Include '*.exe', '*.dll').Count
$tsfBinaryCount = @(Get-ChildItem -LiteralPath $targetTsf -Recurse -File -Include '*.exe', '*.dll').Count
$symbolCount = @(
Get-ChildItem -LiteralPath $targetServer, $targetTsf -Recurse -File -Filter '*.pdb'
).Count
$modeLabel = if ($Light) { '轻量' } else { '完整' }
if (-not $IncludeSymbols) { $modeLabel += ',不含符号' }
Write-Host "安装文件准备完成($modeLabel):$PSScriptRoot"
Write-Host "版本:$TargetVersion;Server EXE/DLL:$serverBinaryCount 个;TSF EXE/DLL:$tsfBinaryCount 个;PDB:$symbolCount 个。"