241 lines
10 KiB
PowerShell
241 lines
10 KiB
PowerShell
[CmdletBinding()]
|
|||
|
|
param(
|
||
|
|
[string]$OutDir = '',
|
||
|
|
[string]$Platform = '',
|
||
|
|
[string]$Version = '',
|
||
|
|
[switch]$NoBuild,
|
||
|
|
[switch]$NoVerify
|
||
|
|
)
|
||
|
|
|
||
|
|
$ErrorActionPreference = 'Stop'
|
||
|
|
|
||
|
|
# Windows 打包脚本:构建、装配、打 zip、出 sha256,产物落 releases\,命名与 CI 一致。
|
||
|
|
|
||
|
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||
|
|
if (-not $OutDir) { $OutDir = Join-Path $repoRoot 'releases' }
|
||
|
|
$OutDir = [System.IO.Path]::GetFullPath($OutDir)
|
||
|
|
|
||
|
|
# 打印错误并退出 1。
|
||
|
|
function Fail([string]$msg) {
|
||
|
|
Write-Host "!! $msg" -ForegroundColor Red
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
# 打印步骤标题。
|
||
|
|
function Step([string]$msg) { Write-Host ''; Write-Host "==> $msg" -ForegroundColor Cyan }
|
||
|
|
# 打印一行输出。
|
||
|
|
function Say([string]$msg) { Write-Host $msg }
|
||
|
|
|
||
|
|
if (-not $Platform) {
|
||
|
|
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x86_64' }
|
||
|
|
$Platform = "windows-$arch"
|
||
|
|
}
|
||
|
|
if ($Platform -notlike 'windows-*') {
|
||
|
|
Fail 'the Linux / macOS package is scripts/package.sh; this script only makes windows-* zips'
|
||
|
|
}
|
||
|
|
if (-not $Version) {
|
||
|
|
$Version = ''
|
||
|
|
}
|
||
|
|
|
||
|
|
# 算文件的 sha256(小写)。
|
||
|
|
function Get-Sha256([string]$path) {
|
||
|
|
(Get-FileHash -Path $path -Algorithm SHA256).Hash.ToLower()
|
||
|
|
}
|
||
|
|
|
||
|
|
# 归档里该有什么,这里先对一遍:两部分都得在,装配漏了东西别等发布出去才发现
|
||
|
|
# 核对归档里必需的文件都在。
|
||
|
|
function Assert-Stage([string]$stage) {
|
||
|
|
$must = @(
|
||
|
|
'bin\chusql_core_storage.dll',
|
||
|
|
'bin\csql.exe',
|
||
|
|
'bin\chusql-web.exe',
|
||
|
|
'bin\chusql-server.exe',
|
||
|
|
'static\index.html',
|
||
|
|
'csql-web.ps1',
|
||
|
|
'scripts\chusql.toml',
|
||
|
|
'install.ps1',
|
||
|
|
'install.sh'
|
||
|
|
)
|
||
|
|
foreach ($rel in $must) {
|
||
|
|
if (-not (Test-Path (Join-Path $stage $rel))) {
|
||
|
|
Fail "$rel is missing from the package"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
# bin 里带 chusql 名字的文件只能是已知产物;改名前的残留或别的垃圾都算装配错误
|
||
|
|
$known = @('chusql_core_storage.dll', 'chusql-server.exe', 'chusql-web.exe')
|
||
|
|
$unknown = @(Get-ChildItem (Join-Path $stage 'bin') -Filter 'chusql*' -File -ErrorAction SilentlyContinue |
|
||
|
|
Where-Object { $known -notcontains $_.Name })
|
||
|
|
if ($unknown.Count -gt 0) {
|
||
|
|
$names = ($unknown | ForEach-Object Name) -join ', '
|
||
|
|
Fail "unexpected artifacts in bin/: $names"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# 真装一遍,核对落地与未选组件。
|
||
|
|
function Test-Installed([string]$name, [string]$comp) {
|
||
|
|
$archive = Join-Path $OutDir "$name.zip"
|
||
|
|
$vtmp = Join-Path ([System.IO.Path]::GetTempPath()) ("chusql-packcheck-" + [Guid]::NewGuid().ToString('N'))
|
||
|
|
$pkg = Join-Path $vtmp 'pkg'
|
||
|
|
$opt = Join-Path $vtmp 'opt'
|
||
|
|
$data = Join-Path $vtmp 'data'
|
||
|
|
$appdata = Join-Path $vtmp 'appdata'
|
||
|
|
New-Item -ItemType Directory -Force -Path $pkg, $opt, $appdata | Out-Null
|
||
|
|
|
||
|
|
$oldAppData = $env:APPDATA
|
||
|
|
$oldLocalAppData = $env:LOCALAPPDATA
|
||
|
|
$oldUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||
|
|
$log = Join-Path $vtmp 'install.log'
|
||
|
|
try {
|
||
|
|
Expand-Archive -Path $archive -DestinationPath $pkg -Force
|
||
|
|
$env:APPDATA = $appdata
|
||
|
|
$env:LOCALAPPDATA = $vtmp
|
||
|
|
$pwsh = Join-Path $PSHOME 'pwsh.exe'
|
||
|
|
if (-not (Test-Path $pwsh)) { $pwsh = 'powershell' }
|
||
|
|
& $pwsh -NoProfile -File (Join-Path $pkg 'install.ps1') -Component $comp -InstallDir $opt `
|
||
|
|
-DataDir $data -RootUser root -RootPassword package-check *>&1 | Out-File -FilePath $log -Encoding UTF8
|
||
|
|
if ($LASTEXITCODE -ne 0) {
|
||
|
|
Get-Content $log | Select-Object -First 120 | Write-Host
|
||
|
|
Fail "$name`: installing the $comp part from the package failed (log above)"
|
||
|
|
}
|
||
|
|
$ok = $true
|
||
|
|
$checks = @(
|
||
|
|
(Join-Path $opt 'bin\chusql_core_storage.dll'),
|
||
|
|
(Join-Path $opt 'bin\chusql-server.exe'),
|
||
|
|
(Join-Path $opt 'init.sql'),
|
||
|
|
(Join-Path $appdata 'ChuSQL\chusql.toml')
|
||
|
|
)
|
||
|
|
$forbidden = @()
|
||
|
|
if ($comp -eq 'web') {
|
||
|
|
$checks += @(
|
||
|
|
(Join-Path $opt 'bin\chusql-web.exe'),
|
||
|
|
(Join-Path $opt 'static\index.html'),
|
||
|
|
(Join-Path $opt 'csql-web.ps1'),
|
||
|
|
(Join-Path $opt 'csql-web.cmd')
|
||
|
|
)
|
||
|
|
$forbidden = @((Join-Path $opt 'bin\csql.exe'), (Join-Path $opt 'csql.cmd'))
|
||
|
|
} else {
|
||
|
|
$checks += @(
|
||
|
|
(Join-Path $opt 'bin\csql.exe'),
|
||
|
|
(Join-Path $opt 'csql.cmd')
|
||
|
|
)
|
||
|
|
$forbidden = @((Join-Path $opt 'bin\chusql-web.exe'), (Join-Path $opt 'static'), (Join-Path $opt 'csql-web.cmd'))
|
||
|
|
}
|
||
|
|
foreach ($f in $checks) { if (-not (Test-Path $f)) { $ok = $false; Write-Host " missing: $f" } }
|
||
|
|
foreach ($f in $forbidden) { if (Test-Path $f) { $ok = $false; Write-Host " should not be there: $f" } }
|
||
|
|
$config = Join-Path $appdata 'ChuSQL\chusql.toml'
|
||
|
|
$expectDataDir = $data -replace '\\', '/'
|
||
|
|
if (-not (Test-Path $config) -or -not (Select-String -Path $config -SimpleMatch "data_dir = ""$expectDataDir""" -Quiet)) {
|
||
|
|
$ok = $false
|
||
|
|
Write-Host " config does not point at $expectDataDir"
|
||
|
|
}
|
||
|
|
if (-not $ok) {
|
||
|
|
Get-Content $log | Select-Object -First 120 | Write-Host
|
||
|
|
Fail "$name`: the $comp part is not what the installer promises (log above)"
|
||
|
|
}
|
||
|
|
# 装的时候往用户 PATH 里写过一行,收尾要原样写回
|
||
|
|
$nowUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||
|
|
if ($nowUserPath -ne $oldUserPath) {
|
||
|
|
[Environment]::SetEnvironmentVariable('Path', $oldUserPath, 'User')
|
||
|
|
Write-Host " note: the installer touched the user PATH; restored it"
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
$env:APPDATA = $oldAppData
|
||
|
|
$env:LOCALAPPDATA = $oldLocalAppData
|
||
|
|
Remove-Item -Recurse -Force $vtmp -ErrorAction SilentlyContinue
|
||
|
|
}
|
||
|
|
Say " ok $name installs the $comp part, and only the $comp part"
|
||
|
|
}
|
||
|
|
|
||
|
|
# ---------- 构建 ----------
|
||
|
|
if (-not $NoBuild) {
|
||
|
|
Step 'Building storage (Rust)'
|
||
|
|
Push-Location (Join-Path $repoRoot 'chusql-core\storage')
|
||
|
|
try {
|
||
|
|
& cargo build --release
|
||
|
|
if ($LASTEXITCODE -ne 0) { Fail 'cargo build --release failed' }
|
||
|
|
} finally { Pop-Location }
|
||
|
|
|
||
|
|
foreach ($target in @('chusql-web:exe:chusql-web', 'chusql-cli:exe:csql', 'chusql-server:exe:chusql-server')) {
|
||
|
|
Step "Building $target (Haskell)"
|
||
|
|
Push-Location (Join-Path $repoRoot 'chusql-cli')
|
||
|
|
try {
|
||
|
|
& stack build --fast $target
|
||
|
|
if ($LASTEXITCODE -ne 0) { Fail "stack build --fast $target failed" }
|
||
|
|
} finally { Pop-Location }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# ---------- 装配 + 打归档 ----------
|
||
|
|
$name = "chusql-$Platform"
|
||
|
|
$stageRoot = Join-Path $OutDir '.stage'
|
||
|
|
$stage = Join-Path $stageRoot $name
|
||
|
|
$zip = Join-Path $OutDir "$name.zip"
|
||
|
|
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||
|
|
|
||
|
|
try {
|
||
|
|
Step "Packing $name"
|
||
|
|
if (Test-Path $stageRoot) { Remove-Item -Recurse -Force $stageRoot }
|
||
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'bin'), (Join-Path $stage 'scripts') | Out-Null
|
||
|
|
|
||
|
|
$storage = Join-Path $repoRoot 'chusql-core\storage\target\release\chusql_core_storage.dll'
|
||
|
|
if (-not (Test-Path $storage)) { Fail 'chusql-core\storage\target\release\chusql_core_storage.dll is not built (run without -NoBuild)' }
|
||
|
|
Copy-Item $storage (Join-Path $stage 'bin')
|
||
|
|
|
||
|
|
# 从真正用于构建的工程目录取安装根(package.sh 同样锚在 chusql-cli),别靠调用者的当前目录
|
||
|
|
Push-Location (Join-Path $repoRoot 'chusql-cli')
|
||
|
|
try { $stackRoot = (& stack path --local-install-root | Select-Object -Last 1).Trim() } finally { Pop-Location }
|
||
|
|
foreach ($frontName in @('chusql-web.exe', 'chusql-server.exe', 'csql.exe')) {
|
||
|
|
$front = Join-Path $stackRoot "bin/$frontName"
|
||
|
|
if (-not (Test-Path $front)) {
|
||
|
|
$front = Get-ChildItem -Path (Join-Path $repoRoot 'chusql-web\.stack-work'), (Join-Path $repoRoot 'chusql-cli\.stack-work'), (Join-Path $repoRoot 'chusql-server\.stack-work') `
|
||
|
|
-Recurse -Filter $frontName -File -ErrorAction SilentlyContinue |
|
||
|
|
Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName
|
||
|
|
}
|
||
|
|
if (-not $front -or -not (Test-Path $front)) { Fail "$frontName is not built (run without -NoBuild)" }
|
||
|
|
Copy-Item $front (Join-Path $stage 'bin')
|
||
|
|
}
|
||
|
|
|
||
|
|
# 第三方 dll 才要连带上:名字带 chusql 的是本 crate 产物或改名前的残留,上面已按名拷过
|
||
|
|
Get-ChildItem (Join-Path $repoRoot 'chusql-core\storage\target\release') -Filter '*.dll' -File -ErrorAction SilentlyContinue |
|
||
|
|
Where-Object { $_.Name -notlike 'chusql*' } |
|
||
|
|
ForEach-Object { Copy-Item $_.FullName (Join-Path $stage 'bin') }
|
||
|
|
|
||
|
|
Copy-Item (Join-Path $repoRoot 'scripts\chusql.toml'), (Join-Path $repoRoot 'scripts\init.sql') (Join-Path $stage 'scripts')
|
||
|
|
Copy-Item (Join-Path $repoRoot 'scripts\install.ps1'), (Join-Path $repoRoot 'scripts\install.sh') $stage
|
||
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'static') | Out-Null
|
||
|
|
Copy-Item (Join-Path $repoRoot 'chusql-web\static\*') (Join-Path $stage 'static') -Recurse
|
||
|
|
Copy-Item (Join-Path $repoRoot 'scripts\csql-web.sh') $stage
|
||
|
|
$webPs1 = Join-Path $repoRoot 'scripts\csql-web.ps1'
|
||
|
|
if (Test-Path $webPs1) { Copy-Item $webPs1 $stage }
|
||
|
|
|
||
|
|
Assert-Stage $stage
|
||
|
|
|
||
|
|
if (Test-Path $zip) { Remove-Item -Force $zip }
|
||
|
|
if (Test-Path "$zip.sha256") { Remove-Item -Force "$zip.sha256" }
|
||
|
|
Compress-Archive -Path (Join-Path $stage '*') -DestinationPath $zip -Force
|
||
|
|
Remove-Item -Recurse -Force $stageRoot
|
||
|
|
|
||
|
|
$hash = Get-Sha256 $zip
|
||
|
|
Set-Content -Path "$zip.sha256" -Value ("{0} {1}" -f $hash, "$name.zip") -NoNewline -Encoding ASCII
|
||
|
|
Say " wrote $zip"
|
||
|
|
Say " checksum $hash"
|
||
|
|
|
||
|
|
if (-not $NoVerify) {
|
||
|
|
Test-Installed $name 'cli'
|
||
|
|
Test-Installed $name 'web'
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
if (Test-Path $stageRoot) { Remove-Item -Recurse -Force $stageRoot }
|
||
|
|
}
|
||
|
|
|
||
|
|
# ---------- 收尾 ----------
|
||
|
|
Step 'Done'
|
||
|
|
Get-ChildItem (Join-Path $OutDir "$name.zip*") | Select-Object Name, Length | Format-Table
|
||
|
|
if ($Version) {
|
||
|
|
Say ''
|
||
|
|
Say "release $Version with (tag 必须是真实存在的 git tag):"
|
||
|
|
Say " gh release create $Version `"$OutDir\*`" --generate-notes"
|
||
|
|
} else {
|
||
|
|
Say ''
|
||
|
|
Say 'publish with (先用 -Version 指定 tag):'
|
||
|
|
Say " gh release create <tag> `"$OutDir\*`" --generate-notes"
|
||
|
|
}
|