mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.1.0 source-ref: master published-at-utc: 2026-03-20T14:14:07Z
400 lines
13 KiB
PowerShell
400 lines
13 KiB
PowerShell
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RepoRoot,
|
|
[string]$ArchOutFile,
|
|
[string]$Version,
|
|
[string]$BuilderBaseImage
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
$script:DockerContextArgs = @()
|
|
|
|
function Invoke-Docker {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$Args,
|
|
[string[]]$ContextArgs,
|
|
[switch]$Quiet,
|
|
[switch]$AllowFailure
|
|
)
|
|
|
|
$effectiveContextArgs = @()
|
|
if ($PSBoundParameters.ContainsKey("ContextArgs")) {
|
|
$effectiveContextArgs = @($ContextArgs)
|
|
}
|
|
else {
|
|
$effectiveContextArgs = @($script:DockerContextArgs)
|
|
}
|
|
|
|
if ($Quiet) {
|
|
& docker @effectiveContextArgs @Args 2>$null
|
|
}
|
|
else {
|
|
& docker @effectiveContextArgs @Args
|
|
}
|
|
$code = $LASTEXITCODE
|
|
if ($code -ne 0 -and -not $AllowFailure) {
|
|
$contextSuffix = ""
|
|
if ($effectiveContextArgs.Count -ge 2) {
|
|
$contextSuffix = " [context: $($effectiveContextArgs[1])]"
|
|
}
|
|
throw "docker$contextSuffix $($Args -join ' ') failed with exit code $code"
|
|
}
|
|
return $code
|
|
}
|
|
|
|
function Invoke-DockerWithRetry {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$Args,
|
|
[int]$MaxAttempts = 3,
|
|
[int]$DelaySeconds = 5
|
|
)
|
|
|
|
$attempt = 0
|
|
while ($attempt -lt $MaxAttempts) {
|
|
$attempt++
|
|
try {
|
|
Invoke-Docker -Args $Args | Out-Null
|
|
return
|
|
}
|
|
catch {
|
|
if ($attempt -ge $MaxAttempts) {
|
|
throw
|
|
}
|
|
|
|
Write-Host " Docker 命令失败,准备重试 ($attempt/$MaxAttempts): $($_.Exception.Message)"
|
|
Start-Sleep -Seconds $DelaySeconds
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-DockerContextCandidates {
|
|
$candidates = @(
|
|
[pscustomobject]@{
|
|
Label = "current"
|
|
ContextArgs = @()
|
|
}
|
|
)
|
|
|
|
$seen = @{}
|
|
$seen["current"] = $true
|
|
|
|
$extraNames = @()
|
|
$currentContextName = ((& docker context show 2>$null) | Out-String).Trim()
|
|
if ($LASTEXITCODE -eq 0 -and $currentContextName -ne "") {
|
|
$extraNames += $currentContextName
|
|
}
|
|
$extraNames += @("default", "desktop-linux")
|
|
|
|
foreach ($name in $extraNames) {
|
|
$trimmed = ([string]$name).Trim()
|
|
if ($trimmed -eq "" -or $seen.ContainsKey($trimmed)) {
|
|
continue
|
|
}
|
|
|
|
try {
|
|
& docker context inspect $trimmed *> $null
|
|
}
|
|
catch {
|
|
continue
|
|
}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
continue
|
|
}
|
|
|
|
$seen[$trimmed] = $true
|
|
$candidates += [pscustomobject]@{
|
|
Label = $trimmed
|
|
ContextArgs = @("--context", $trimmed)
|
|
}
|
|
}
|
|
|
|
return $candidates
|
|
}
|
|
|
|
function Get-DockerInfo {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Label,
|
|
[AllowEmptyCollection()]
|
|
[string[]]$ContextArgs
|
|
)
|
|
|
|
$format = "{{.OSType}}|{{.Architecture}}"
|
|
$output = ""
|
|
$code = 1
|
|
try {
|
|
$output = (& docker @ContextArgs info --format $format 2>$null | Out-String).Trim()
|
|
$code = $LASTEXITCODE
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
|
|
if ($code -ne 0 -or $output -eq "") {
|
|
return $null
|
|
}
|
|
|
|
$parts = $output -split "\|", 2
|
|
$osType = ""
|
|
$architecture = ""
|
|
if ($parts.Length -ge 1) {
|
|
$osType = $parts[0].Trim().ToLowerInvariant()
|
|
}
|
|
if ($parts.Length -ge 2) {
|
|
$architecture = $parts[1].Trim()
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
Label = $Label
|
|
ContextArgs = @($ContextArgs)
|
|
OSType = $osType
|
|
Architecture = $architecture
|
|
}
|
|
}
|
|
|
|
function Resolve-DockerLinuxContext {
|
|
param(
|
|
[int]$Attempts = 1,
|
|
[int]$DelaySeconds = 0
|
|
)
|
|
|
|
$lastReadyContexts = @()
|
|
|
|
for ($attempt = 0; $attempt -lt $Attempts; $attempt++) {
|
|
$readyContexts = @()
|
|
|
|
foreach ($candidate in Get-DockerContextCandidates) {
|
|
$info = Get-DockerInfo -Label $candidate.Label -ContextArgs $candidate.ContextArgs
|
|
if ($null -eq $info) {
|
|
continue
|
|
}
|
|
|
|
$readyContexts += $info
|
|
if ($info.OSType -eq "linux") {
|
|
return [pscustomobject]@{
|
|
LinuxContext = $info
|
|
ReadyContexts = $readyContexts
|
|
}
|
|
}
|
|
}
|
|
|
|
$lastReadyContexts = $readyContexts
|
|
if ($attempt -lt ($Attempts - 1)) {
|
|
Start-Sleep -Seconds $DelaySeconds
|
|
}
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
LinuxContext = $null
|
|
ReadyContexts = $lastReadyContexts
|
|
}
|
|
}
|
|
|
|
function Get-EnvOrDefault {
|
|
param(
|
|
[string]$Name,
|
|
[string]$DefaultValue
|
|
)
|
|
|
|
$value = [Environment]::GetEnvironmentVariable($Name)
|
|
if ($null -eq $value) {
|
|
return $DefaultValue
|
|
}
|
|
|
|
$trimmed = $value.Trim()
|
|
if ($trimmed -eq "") {
|
|
return $DefaultValue
|
|
}
|
|
|
|
return $trimmed
|
|
}
|
|
|
|
try {
|
|
Write-Host "[Linux] 检查 Docker Desktop 环境..."
|
|
if (-not (Get-Command docker.exe -ErrorAction SilentlyContinue)) {
|
|
throw "未检测到 docker.exe,无法执行 Linux 打包`n 请先安装 Docker Desktop,并启用 Linux 容器引擎"
|
|
}
|
|
|
|
$dockerResolution = Resolve-DockerLinuxContext
|
|
if ($null -eq $dockerResolution.LinuxContext) {
|
|
$dockerDesktopExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
|
if (Test-Path -LiteralPath $dockerDesktopExe) {
|
|
$dockerDesktopRunning = @(Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue).Count -gt 0
|
|
if ($dockerDesktopRunning) {
|
|
Write-Host " Docker Desktop 已在运行,等待 Linux 引擎就绪..."
|
|
}
|
|
else {
|
|
Write-Host " Docker Linux 引擎未就绪,尝试启动 Docker Desktop..."
|
|
Start-Process -FilePath $dockerDesktopExe | Out-Null
|
|
}
|
|
$dockerResolution = Resolve-DockerLinuxContext -Attempts 24 -DelaySeconds 5
|
|
}
|
|
}
|
|
|
|
if ($null -eq $dockerResolution.LinuxContext) {
|
|
$readyContexts = @($dockerResolution.ReadyContexts)
|
|
if ($readyContexts.Count -gt 0) {
|
|
$readySummary = ($readyContexts | ForEach-Object { "$($_.Label) [$($_.OSType)]" }) -join ", "
|
|
throw "Docker 已启动,但当前可用上下文不是 Linux 容器: $readySummary`n 请在 Docker Desktop 中切换到 Linux containers 后重试"
|
|
}
|
|
|
|
throw "Docker 引擎未就绪`n 请先启动 Docker Desktop,并确认 Linux 容器引擎可用"
|
|
}
|
|
|
|
$script:DockerContextArgs = @($dockerResolution.LinuxContext.ContextArgs)
|
|
$contextLabel = $dockerResolution.LinuxContext.Label
|
|
$dockerArchRaw = ([string]$dockerResolution.LinuxContext.Architecture).Trim()
|
|
$linuxArch = switch ($dockerArchRaw.ToLowerInvariant()) {
|
|
"x86_64" { "amd64" }
|
|
"amd64" { "amd64" }
|
|
"aarch64" { "arm64" }
|
|
"arm64" { "arm64" }
|
|
default { "" }
|
|
}
|
|
|
|
if ($linuxArch -eq "") {
|
|
throw "无法识别 Docker Linux 架构`n 当前输出: $dockerArchRaw"
|
|
}
|
|
|
|
$repoDocker = ($RepoRoot -replace "\\", "/").Trim()
|
|
if ($repoDocker -eq "") {
|
|
throw "无法转换仓库路径为 Docker 挂载路径"
|
|
}
|
|
|
|
Write-Host "✓ Docker Desktop 就绪"
|
|
Write-Host " Docker 上下文: $contextLabel"
|
|
Write-Host " 仓库挂载: $repoDocker"
|
|
Write-Host " Linux 架构: $linuxArch"
|
|
Write-Host ""
|
|
|
|
$builderImage = "ant-browser-linux-builder:local"
|
|
$dockerfilePath = Join-Path $RepoRoot "publish/linux/linux-builder.Dockerfile"
|
|
$resolvedBuilderBaseImage = $BuilderBaseImage
|
|
if (-not $resolvedBuilderBaseImage -or $resolvedBuilderBaseImage.Trim() -eq "") {
|
|
$resolvedBuilderBaseImage = $env:ANT_BROWSER_LINUX_BUILDER_BASE_IMAGE
|
|
}
|
|
if (-not $resolvedBuilderBaseImage -or $resolvedBuilderBaseImage.Trim() -eq "") {
|
|
$resolvedBuilderBaseImage = "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/golang:1.25-bookworm"
|
|
}
|
|
$resolvedBuilderBaseImage = $resolvedBuilderBaseImage.Trim()
|
|
|
|
$dockerfileHashRaw = (Get-FileHash -LiteralPath $dockerfilePath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$dockerfileHash = [System.BitConverter]::ToString(
|
|
[System.Security.Cryptography.SHA256]::Create().ComputeHash(
|
|
[System.Text.Encoding]::UTF8.GetBytes("$dockerfileHashRaw|$resolvedBuilderBaseImage")
|
|
)
|
|
).Replace("-", "").ToLowerInvariant()
|
|
$builderHashLabel = "ant.browser.builder.hash"
|
|
$builderHashCurrent = ""
|
|
$builderInspectJson = ""
|
|
$builderInspectExitCode = 1
|
|
try {
|
|
$builderInspectJson = & docker @script:DockerContextArgs image inspect $builderImage 2>$null
|
|
$builderInspectExitCode = $LASTEXITCODE
|
|
}
|
|
catch {
|
|
$builderInspectExitCode = 1
|
|
}
|
|
|
|
if ($builderInspectExitCode -eq 0) {
|
|
$builderInspectItems = @($builderInspectJson | Out-String | ConvertFrom-Json)
|
|
if ($builderInspectItems.Count -gt 0) {
|
|
$config = $builderInspectItems[0].Config
|
|
if ($null -ne $config) {
|
|
$labelsProperty = $config.PSObject.Properties["Labels"]
|
|
if ($labelsProperty -and $null -ne $labelsProperty.Value) {
|
|
$labels = $labelsProperty.Value
|
|
if ($labels.PSObject.Properties.Name -contains $builderHashLabel) {
|
|
$builderHashCurrent = [string]$labels.PSObject.Properties[$builderHashLabel].Value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$needsBuilderImage = ($builderInspectExitCode -ne 0) -or ($builderHashCurrent -ne $dockerfileHash)
|
|
|
|
if ($needsBuilderImage) {
|
|
Write-Host "[Linux] 构建 Linux builder 镜像..."
|
|
Write-Host " Dockerfile: publish/linux/linux-builder.Dockerfile"
|
|
Write-Host " 基础镜像: $resolvedBuilderBaseImage"
|
|
Write-Host ""
|
|
Invoke-DockerWithRetry -Args @(
|
|
"build",
|
|
"--build-arg", "BUILDER_BASE_IMAGE=$resolvedBuilderBaseImage",
|
|
"--label", "$builderHashLabel=$dockerfileHash",
|
|
"-f", "publish/linux/linux-builder.Dockerfile",
|
|
"-t", $builderImage,
|
|
"publish/linux"
|
|
)
|
|
}
|
|
else {
|
|
Write-Host "[Linux] 复用已有 builder 镜像缓存"
|
|
Write-Host " 镜像: $builderImage"
|
|
Write-Host " 基础镜像: $resolvedBuilderBaseImage"
|
|
Write-Host ""
|
|
}
|
|
|
|
$npmCacheVolume = "ant-browser-linux-npm-cache"
|
|
$nodeModulesVolume = "ant-browser-linux-node-modules-$linuxArch"
|
|
$goModCacheVolume = "ant-browser-linux-go-mod-cache"
|
|
$goBuildCacheVolume = "ant-browser-linux-go-build-cache"
|
|
$runtimeCpuLimit = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_DOCKER_CPUS" -DefaultValue "2"
|
|
$runtimeMemoryLimit = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_DOCKER_MEMORY" -DefaultValue "1408m"
|
|
$runtimeMemorySwapLimit = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_DOCKER_MEMORY_SWAP" -DefaultValue "1792m"
|
|
$runtimeShmSize = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_DOCKER_SHM_SIZE" -DefaultValue "256m"
|
|
$nodeMaxOldSpace = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_NODE_MAX_OLD_SPACE" -DefaultValue "384"
|
|
$goMaxProcs = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_GOMAXPROCS" -DefaultValue "2"
|
|
$npmJobs = Get-EnvOrDefault -Name "ANT_BROWSER_LINUX_NPM_JOBS" -DefaultValue "1"
|
|
$versionArgText = ""
|
|
if ($Version -and $Version.Trim() -ne "") {
|
|
$versionArgText = " --version $Version"
|
|
}
|
|
$linuxPublishCommand = 'for f in publish/linux/*.sh tools/runtime/*.sh; do if [ -f "$f" ]; then sed -i ''s/\r$//'' "$f"; fi; done && bash publish/linux/publish-linux.sh --arch {0}{1}' -f $linuxArch, $versionArgText
|
|
|
|
Write-Host "[Linux] 通过 Docker Desktop 执行发布脚本..."
|
|
Write-Host " 镜像: $builderImage"
|
|
Write-Host " 挂载: $repoDocker > /workspace"
|
|
Write-Host " 缓存卷: $npmCacheVolume, $nodeModulesVolume, $goModCacheVolume, $goBuildCacheVolume"
|
|
Write-Host " 资源限制: CPUs=$runtimeCpuLimit, Memory=$runtimeMemoryLimit, Swap=$runtimeMemorySwapLimit, /dev/shm=$runtimeShmSize"
|
|
Write-Host " 进程限制: NODE_OPTIONS=--max-old-space-size=$nodeMaxOldSpace, GOMAXPROCS=$goMaxProcs, npm_config_jobs=$npmJobs"
|
|
Write-Host " 命令: 先清理 .sh 换行符,再执行 bash publish/linux/publish-linux.sh --arch $linuxArch$versionArgText"
|
|
Write-Host ""
|
|
Invoke-Docker -Args @(
|
|
"run",
|
|
"--rm",
|
|
"--cpus", $runtimeCpuLimit,
|
|
"--memory", $runtimeMemoryLimit,
|
|
"--memory-swap", $runtimeMemorySwapLimit,
|
|
"--shm-size", $runtimeShmSize,
|
|
"-e", "CI=1",
|
|
"-e", "NODE_OPTIONS=--max-old-space-size=$nodeMaxOldSpace",
|
|
"-e", "GOMAXPROCS=$goMaxProcs",
|
|
"-e", "npm_config_jobs=$npmJobs",
|
|
"-v", "${repoDocker}:/workspace",
|
|
"-v", "${npmCacheVolume}:/root/.npm",
|
|
"-v", "${nodeModulesVolume}:/workspace/frontend/node_modules",
|
|
"-v", "${goModCacheVolume}:/go/pkg/mod",
|
|
"-v", "${goBuildCacheVolume}:/root/.cache/go-build",
|
|
"-w", "/workspace",
|
|
$builderImage,
|
|
"bash",
|
|
"-c",
|
|
$linuxPublishCommand
|
|
) | Out-Null
|
|
|
|
if ($ArchOutFile -and $ArchOutFile.Trim() -ne "") {
|
|
Set-Content -LiteralPath $ArchOutFile -Value $linuxArch -NoNewline -Encoding ascii
|
|
}
|
|
|
|
Write-Host "✓ Linux 产物生成成功"
|
|
exit 0
|
|
}
|
|
catch {
|
|
Write-Host "✗ $($_.Exception.Message)"
|
|
exit 1
|
|
}
|