feat(client): improve SDK packaging and registry config

This commit is contained in:
2026-07-14 01:37:06 +00:00
parent cb5819ab7c
commit 94b20bf2bb
38 changed files with 3040 additions and 3960 deletions
+33
View File
@@ -0,0 +1,33 @@
客户端脚本说明
==============
本目录保存 update-client 的辅助脚本。项目根目录只保留源码、CMake 入口、Docs 和配置模板,脚本统一放在这里。
脚本列表:
1. package-sdk.ps1
生成给其他软件接入用的 UpdateClientSDK 包。
2. package-client.ps1
生成某个具体产品的最终客户端发布包。
3. install-sdk.ps1
将 SDK 运行时复制到业务软件 Release 目录。
推荐在 update-client 根目录执行:
```powershell
.\scripts\package-sdk.ps1 `
-SourceDir .\out\bin `
-OutputDir .\dist\UpdateClientSDK `
-ZipFile .\dist\UpdateClientSDK.zip `
-SdkVersion 0.1.0
```
```powershell
.\scripts\package-client.ps1 `
-SourceDir .\out\bin `
-ConfigFile .\config\app_config.json `
-OutputDir .\dist\UpdateClient `
-ZipFile .\dist\UpdateClient.zip
```
+74
View File
@@ -0,0 +1,74 @@
param(
[Parameter(Mandatory=$true)]
[string]$SdkRoot,
[string]$ReleaseDir = (Get-Location).Path,
[switch]$OverwriteConfig,
[switch]$IncludeQtRuntime
)
$ErrorActionPreference = "Stop"
$sdk = (Resolve-Path $SdkRoot).Path
$release = (Resolve-Path $ReleaseDir).Path
$binDir = Join-Path $sdk "bin"
$configDir = Join-Path $sdk "config"
$appConfig = Join-Path $configDir "app_config.json"
$publicKey = Join-Path $configDir "manifest_public_key.pem"
foreach ($path in @($binDir, $appConfig, $publicKey)) {
if (-not (Test-Path $path)) {
throw "SDK file is missing: $path"
}
}
function Test-IsQtRuntimeItem {
param([System.IO.FileSystemInfo]$Item)
if ($IncludeQtRuntime) {
return $false
}
if ($Item.PSIsContainer) {
return $Item.Name -in @(
"bearer",
"iconengines",
"imageformats",
"platforms",
"styles",
"translations"
)
}
return (
$Item.Name -match '^Qt5.*\.dll$' -or
$Item.Name -in @(
"libEGL.dll",
"libGLESv2.dll",
"opengl32sw.dll",
"d3dcompiler_47.dll"
)
)
}
Get-ChildItem $binDir -Force | Where-Object {
-not (Test-IsQtRuntimeItem $_)
} | Copy-Item -Destination $release -Recurse -Force
$targetConfigDir = Join-Path $release "config"
New-Item $targetConfigDir -ItemType Directory -Force | Out-Null
$targetAppConfig = Join-Path $targetConfigDir "app_config.json"
if ((-not (Test-Path $targetAppConfig)) -or $OverwriteConfig) {
Copy-Item $appConfig $targetAppConfig -Force
} else {
Write-Host "Keep existing config/app_config.json. Use -OverwriteConfig to replace it."
}
Copy-Item $publicKey (Join-Path $targetConfigDir "manifest_public_key.pem") -Force
Write-Host "SDK files installed to: $release"
Write-Host "Next: edit config/app_config.json, then start Launcher.exe."
+149
View File
@@ -0,0 +1,149 @@
param(
[string]$SourceDir = "",
[Parameter(Mandatory = $true)]
[string]$ConfigFile,
[string]$OutputDir = "",
[string]$ZipFile = ""
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
if ([string]::IsNullOrWhiteSpace($SourceDir)) {
$SourceDir = Join-Path $RepoRoot "out/bin"
}
if ([string]::IsNullOrWhiteSpace($OutputDir)) {
$OutputDir = Join-Path $RepoRoot "dist/UpdateClient"
}
if ([string]::IsNullOrWhiteSpace($ZipFile)) {
$ZipFile = Join-Path $RepoRoot "dist/UpdateClient.zip"
}
$source = (Resolve-Path $SourceDir).Path
$config = (Resolve-Path $ConfigFile).Path
$settings = Get-Content $config -Raw -Encoding UTF8 | ConvertFrom-Json
function Normalize-RelativePath([string]$PathValue) {
return ($PathValue -replace '\\', '/').Trim('/')
}
function Get-RelativePathUnderSource([string]$FullPath, [string]$Fallback) {
$full = (Resolve-Path $FullPath).Path
if ($full.StartsWith($source, [System.StringComparison]::OrdinalIgnoreCase)) {
return Normalize-RelativePath ($full.Substring($source.Length).TrimStart('\', '/'))
}
return Normalize-RelativePath $Fallback
}
$configRelativePath = Get-RelativePathUnderSource $config "config/app_config.json"
$configRelativeParent = Split-Path $configRelativePath -Parent
$runtimeDirRelative = Normalize-RelativePath (Split-Path $configRelativeParent -Parent)
if ($runtimeDirRelative -eq ".") { $runtimeDirRelative = "" }
function Join-RelativePath([string]$Base, [string]$Child) {
$baseNorm = Normalize-RelativePath $Base
$childNorm = Normalize-RelativePath $Child
if ([string]::IsNullOrWhiteSpace($baseNorm)) { return $childNorm }
if ([string]::IsNullOrWhiteSpace($childNorm)) { return $baseNorm }
return "$baseNorm/$childNorm"
}
$requiredFields = @(
"app_id", "channel", "api_base_url", "current_version",
"client_token", "launch_token", "license_key",
"main_executable", "launcher_executable", "updater_executable", "bootstrap_executable"
)
foreach ($field in $requiredFields) {
if (-not $settings.$field) {
throw "Config file is missing required field: $field"
}
}
$mainExecutable = Normalize-RelativePath $settings.main_executable
$launcherExecutable = Normalize-RelativePath $settings.launcher_executable
$updaterExecutable = Normalize-RelativePath $settings.updater_executable
$bootstrapExecutable = Normalize-RelativePath $settings.bootstrap_executable
$requiredFiles = @(
(Join-RelativePath $runtimeDirRelative $mainExecutable),
(Join-RelativePath $runtimeDirRelative $launcherExecutable),
(Join-RelativePath $runtimeDirRelative $updaterExecutable),
(Join-RelativePath $runtimeDirRelative $bootstrapExecutable)
)
foreach ($name in $requiredFiles) {
$nativeName = $name -replace '/', [IO.Path]::DirectorySeparatorChar
if (-not (Test-Path (Join-Path $source $nativeName))) {
throw "Source directory is missing required file: $name"
}
}
$debugArtifacts = Get-ChildItem $source -Recurse -File | Where-Object {
$_.Name -match '^(Qt5.*d|qwindowsd|libEGLd|libGLESv2d|msvcp.*d|vcruntime.*d)\.dll$' -or
$_.Extension -in @('.pdb', '.ilk')
}
if ($debugArtifacts) {
throw "Source directory contains Debug artifacts. Clean out/bin and rebuild Release first. Example: $($debugArtifacts[0].FullName)"
}
$expectedMainPath = Join-RelativePath $runtimeDirRelative $mainExecutable
$mainLeafName = Split-Path $mainExecutable -Leaf
$duplicateMain = Get-ChildItem $source -Recurse -File -Filter $mainLeafName | Where-Object {
$relative = Normalize-RelativePath ($_.FullName.Substring($source.Length).TrimStart('\', '/'))
$relative -ne $expectedMainPath
} | Select-Object -First 1
if ($duplicateMain) {
throw "Source directory contains a duplicate main executable outside $expectedMainPath. Use a clean Release root directory: $($duplicateMain.FullName)"
}
$manifestName = "manifest_$($settings.current_version).json"
$sourceManifestRelative = Join-RelativePath $runtimeDirRelative "update/manifest_cache/$manifestName"
$sourceManifest = Join-Path $source ($sourceManifestRelative -replace '/', [IO.Path]::DirectorySeparatorChar)
if (-not (Test-Path $sourceManifest)) {
$legacySourceManifest = Join-Path $source "update/manifest_cache/$manifestName"
if (Test-Path $legacySourceManifest) {
$sourceManifest = $legacySourceManifest
}
}
if (-not (Test-Path $sourceManifest)) {
throw "Missing signed Manifest cache for current version: $sourceManifest. Complete online update/verification for this version before packaging."
}
if (Test-Path $OutputDir) {
Remove-Item $OutputDir -Recurse -Force
}
New-Item $OutputDir -ItemType Directory -Force | Out-Null
Get-ChildItem $source -Force | Where-Object {
$_.Name -notin @("update", "update_temp")
} | Copy-Item -Destination $OutputDir -Recurse -Force
$outputRuntimeUpdateDir = Join-Path $OutputDir ((Join-RelativePath $runtimeDirRelative "update") -replace '/', [IO.Path]::DirectorySeparatorChar)
if (Test-Path $outputRuntimeUpdateDir) {
Remove-Item $outputRuntimeUpdateDir -Recurse -Force
}
$outputRuntimeTempDir = Join-Path $OutputDir ((Join-RelativePath $runtimeDirRelative "update_temp") -replace '/', [IO.Path]::DirectorySeparatorChar)
if (Test-Path $outputRuntimeTempDir) {
Remove-Item $outputRuntimeTempDir -Recurse -Force
}
$outputConfigPath = Join-Path $OutputDir ($configRelativePath -replace '/', [IO.Path]::DirectorySeparatorChar)
$outputConfigDir = Split-Path $outputConfigPath -Parent
New-Item $outputConfigDir -ItemType Directory -Force | Out-Null
Copy-Item $config $outputConfigPath -Force
@("client_identity.dat", "local_state.json", "version_policy.dat") | ForEach-Object {
$runtimeFile = Join-Path $outputConfigDir $_
if (Test-Path $runtimeFile) { Remove-Item $runtimeFile -Force }
}
$manifestDir = Join-Path $OutputDir ((Join-RelativePath $runtimeDirRelative "update/manifest_cache") -replace '/', [IO.Path]::DirectorySeparatorChar)
New-Item $manifestDir -ItemType Directory -Force | Out-Null
Copy-Item $sourceManifest (Join-Path $manifestDir $manifestName) -Force
$zipParent = Split-Path $ZipFile -Parent
New-Item $zipParent -ItemType Directory -Force | Out-Null
if (Test-Path $ZipFile) { Remove-Item $ZipFile -Force }
Compress-Archive -Path (Join-Path $OutputDir "*") -DestinationPath $ZipFile -CompressionLevel Optimal
Write-Host "Package directory: $OutputDir"
Write-Host "ZIP file: $ZipFile"
+133
View File
@@ -0,0 +1,133 @@
param(
[string]$SourceDir = "",
[string]$OutputDir = "",
[string]$ZipFile = "",
[string]$SdkVersion = "0.1.0",
[string]$ExampleConfig = "",
[switch]$IncludeDemoMainApp,
[switch]$IncludeQtRuntime
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
if ([string]::IsNullOrWhiteSpace($SourceDir)) {
$SourceDir = Join-Path $RepoRoot "out/bin"
}
if ([string]::IsNullOrWhiteSpace($OutputDir)) {
$OutputDir = Join-Path $RepoRoot "dist/UpdateClientSDK"
}
if ([string]::IsNullOrWhiteSpace($ZipFile)) {
$ZipFile = Join-Path $RepoRoot "dist/UpdateClientSDK.zip"
}
if ([string]::IsNullOrWhiteSpace($ExampleConfig)) {
$ExampleConfig = Join-Path $RepoRoot "config/app_config.example.json"
}
$source = (Resolve-Path $SourceDir).Path
$exampleConfigPath = (Resolve-Path $ExampleConfig).Path
$requiredFiles = @("Launcher.exe", "Updater.exe", "Bootstrap.exe")
foreach ($name in $requiredFiles) {
$path = Join-Path $source $name
if (-not (Test-Path $path)) {
throw "SDK source directory is missing required file: $path"
}
}
$publicKeyCandidates = @(
(Join-Path $source "config/manifest_public_key.pem"),
(Join-Path $source "manifest_public_key.pem"),
(Join-Path $RepoRoot "config/manifest_public_key.pem")
)
$publicKey = $publicKeyCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $publicKey) {
throw "manifest_public_key.pem is missing. Prepare the public key that matches the server signing private key."
}
$debugArtifacts = Get-ChildItem $source -Recurse -File | Where-Object {
$_.Name -match '^(Qt5.*d|qwindowsd|libEGLd|libGLESv2d|msvcp.*d|vcruntime.*d)\.dll$' -or
$_.Extension -in @('.pdb', '.ilk')
}
if ($debugArtifacts) {
throw "SDK source directory contains Debug artifacts. Use a clean Release output directory. Example: $($debugArtifacts[0].FullName)"
}
if (Test-Path $OutputDir) { Remove-Item $OutputDir -Recurse -Force }
New-Item $OutputDir -ItemType Directory -Force | Out-Null
$binDir = Join-Path $OutputDir "bin"
$configDir = Join-Path $OutputDir "config"
$scriptsDir = Join-Path $OutputDir "scripts"
New-Item $binDir,$configDir,$scriptsDir -ItemType Directory -Force | Out-Null
$excludedTopLevel = @("config", "update", "update_temp", "manifest_public_key.pem")
if (-not $IncludeDemoMainApp) { $excludedTopLevel += "MainApp.exe" }
if (-not $IncludeQtRuntime) {
$excludedTopLevel += @(
"bearer",
"iconengines",
"imageformats",
"platforms",
"styles",
"translations"
)
}
function Test-IsQtRuntimeFile {
param([System.IO.FileSystemInfo]$Item)
if ($IncludeQtRuntime -or $Item.PSIsContainer) {
return $false
}
return (
$Item.Name -match '^Qt5.*\.dll$' -or
$Item.Name -in @(
"libEGL.dll",
"libGLESv2.dll",
"opengl32sw.dll",
"d3dcompiler_47.dll"
)
)
}
Get-ChildItem $source -Force | Where-Object {
$_.Name -notin $excludedTopLevel -and -not (Test-IsQtRuntimeFile $_)
} | Copy-Item -Destination $binDir -Recurse -Force
Copy-Item $exampleConfigPath (Join-Path $configDir "app_config.json") -Force
Copy-Item $publicKey (Join-Path $configDir "manifest_public_key.pem") -Force
$wordGuideSource = @($RepoRoot, (Join-Path $RepoRoot "Docs")) |
Where-Object { Test-Path $_ } |
ForEach-Object { Get-ChildItem $_ -File -Filter "*.docx" } |
Where-Object { $_.Name -like "*SDK*.docx" -and $_.Name -notlike "~$*" } |
Sort-Object Name |
Select-Object -First 1
if (-not $wordGuideSource) {
throw "SDK integration Word guide is missing. Expected a *SDK*.docx file in the repository root or Docs directory."
}
Copy-Item $wordGuideSource.FullName (Join-Path $OutputDir $wordGuideSource.Name) -Force
Copy-Item (Join-Path $PSScriptRoot "package-client.ps1") (Join-Path $scriptsDir "package-client.ps1") -Force
Copy-Item (Join-Path $PSScriptRoot "package-sdk.ps1") (Join-Path $scriptsDir "package-sdk.ps1") -Force
Copy-Item (Join-Path $PSScriptRoot "install-sdk.ps1") (Join-Path $scriptsDir "install-sdk.ps1") -Force
@{
sdk_version = $SdkVersion
generated_at = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
sdk_type = "external-updater-runtime"
required_entry = "Launcher.exe"
contains_demo_main_app = [bool]$IncludeDemoMainApp
} | ConvertTo-Json -Depth 3 | Set-Content (Join-Path $OutputDir "sdk_manifest.json") -Encoding UTF8
$zipParent = Split-Path $ZipFile -Parent
New-Item $zipParent -ItemType Directory -Force | Out-Null
if (Test-Path $ZipFile) { Remove-Item $ZipFile -Force }
Compress-Archive -Path (Join-Path $OutputDir "*") -DestinationPath $ZipFile -CompressionLevel Optimal
Write-Host "SDK directory: $OutputDir"
Write-Host "SDK zip: $ZipFile"
Write-Host "SDK version: $SdkVersion"