Files
m 20d361a886
build / backend (push) Has been cancelled
build / node-agent (push) Has been cancelled
build / worker (push) Has been cancelled
рабочий вариант, но скороть 10 МБит
2026-05-22 21:46:49 +03:00

420 lines
15 KiB
PowerShell

#requires -Version 5
param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
[string]$BuildType = "release",
[string]$AndroidHome = $env:ANDROID_HOME,
[switch]$SkipWorkspaceCleanup,
[switch]$PrintOnly,
[bool]$PublishToTestDockerDownloads = $false,
[string]$TestDockerSshAlias = "test-docker",
[string]$TestDockerDownloadPath = "/tmp/rap-web-admin/html/downloads",
[string]$TestDockerBackendReleasePath = "/tmp/rap-release-0.2.309-latencyaware"
)
$ErrorActionPreference = "Stop"
function Fail([string]$Message) {
Write-Error $Message
exit 1
}
function Resolve-AndroidSdk([string]$CandidateHome) {
$candidates = @(
$CandidateHome,
"C:\Android\Sdk",
"$env:LOCALAPPDATA\Android\Sdk",
"$env:USERPROFILE\AppData\Local\Android\Sdk"
) | Where-Object { $_ -and (Test-Path $_) } | Select-Object -Unique
foreach ($candidate in $candidates) {
if ((Test-Path (Join-Path $candidate "platform-tools")) -and (Test-Path (Join-Path $candidate "platforms"))) {
return $candidate
}
}
if ($candidates.Count -gt 0) {
return $candidates[0]
}
return $null
}
function Get-GradleBinary {
$gradleCandidate = Get-Command gradle -ErrorAction SilentlyContinue
if ($null -eq $gradleCandidate) {
Fail "Gradle не найден в PATH. Установи Gradle (`winget install Gradle.Gradle` или `choco install gradle`)."
}
return $gradleCandidate.Source
}
function Get-JavaVersion {
$javaCandidate = Get-Command java -ErrorAction SilentlyContinue
if ($null -eq $javaCandidate) {
Fail "Java не найден в PATH. Нужен JDK 17+."
}
$escapedJava = $javaCandidate.Source.Replace('"', '\"')
$versionLine = & cmd.exe /c "`"$escapedJava`" -version 2>&1" | Select-Object -First 1
if ($versionLine -match "version\s+`"(\d+)\.") {
return $matches[1]
}
return "unknown"
}
function Resolve-SdkComponent([string]$Root, [string]$RelativePath) {
$candidate = Join-Path $Root $RelativePath
if (Test-Path $candidate) {
return $candidate
}
return $null
}
function Get-AndroidVersionInfo([string]$AndroidProjectDir) {
$moduleBuildFile = Join-Path $AndroidProjectDir "app\build.gradle"
if (-not (Test-Path $moduleBuildFile)) {
Fail "Не найден файл app/build.gradle: $moduleBuildFile"
}
$content = Get-Content $moduleBuildFile -Raw
$versionCodeMatch = [regex]::Match($content, '(?m)^\s*versionCode\s+([0-9]+)\s*$')
$versionNameMatch = [regex]::Match($content, '(?m)^\s*versionName\s+"([^"]+)"\s*$')
if (-not $versionCodeMatch.Success -or -not $versionNameMatch.Success) {
Fail "Не удалось прочитать versionCode/versionName из app/build.gradle"
}
return @{
VersionCode = [int]$versionCodeMatch.Groups[1].Value
VersionName = $versionNameMatch.Groups[1].Value.Trim()
}
}
function Publish-Artifact {
param(
[string]$SourcePath,
[string]$PublishRoot,
[string]$BuildTypeNormalized,
[string]$VersionName,
[string]$VersionFileName,
[string]$LatestFileName,
[string]$PublishedPathPrefix,
[string]$VersionTag
)
New-Item -ItemType Directory -Force -Path $PublishRoot | Out-Null
$versionDir = Join-Path $PublishRoot "releases\$VersionName"
New-Item -ItemType Directory -Force -Path $versionDir | Out-Null
$latestPath = Join-Path $PublishRoot $LatestFileName
$versionPath = Join-Path $versionDir $VersionFileName
$metaPath = Join-Path $PublishRoot "rap-android-vpn-build.json"
Copy-Item -Path $SourcePath -Destination $latestPath -Force
Copy-Item -Path $SourcePath -Destination $versionPath -Force
$hash = (Get-FileHash -Algorithm SHA256 -Path $SourcePath).Hash.ToLowerInvariant()
$size = (Get-Item $SourcePath).Length
$published = [ordered]@{
version = @{
name = $VersionName
code = $VersionTag
}
build = @{
type = $BuildTypeNormalized
artifact = "app-$BuildTypeNormalized.apk"
}
published = @{
timestamp_utc = (Get-Date).ToUniversalTime().ToString("o")
path = ("$PublishedPathPrefix/$LatestFileName").TrimStart("/")
size_bytes = $size
sha256 = $hash
}
release_paths = @{
latest = $LatestFileName
versioned = "releases/$VersionName/$VersionFileName"
}
}
$manifestJson = ($published | ConvertTo-Json -Depth 6) + [Environment]::NewLine
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($metaPath, $manifestJson, $utf8NoBom)
return @{
latest = $latestPath
versioned = $versionPath
summary = $metaPath
hash = $hash
size = $size
artifacts = @(
$latestPath,
$versionPath,
$metaPath
)
releaseDir = $versionDir
}
}
function Resolve-RemoteDirectory([string]$RemotePath) {
return $RemotePath.Trim()
}
function Ensure-RemoteDirectory {
param(
[string]$RemoteHost,
[string]$RemotePath
)
if (-not $RemoteHost -or -not $RemotePath) {
Fail "Remote host/path must be set for publish sync."
}
$escapedPath = $RemotePath.Replace("'", "''")
& ssh $RemoteHost "mkdir -p '$escapedPath'" | Out-Null
if ($LASTEXITCODE -ne 0) {
Fail "Не удалось создать каталог на remote хосте: $RemotePath"
}
}
function Publish-ToTestDockerDownloads {
param(
[string]$RemoteHost,
[string]$RemoteRoot,
[string]$VersionName,
[string]$LatestFile,
[string]$VersionFile,
[string]$ManifestFile
)
Ensure-RemoteDirectory -RemoteHost $RemoteHost -RemotePath $RemoteRoot
$remoteVersionDir = "$RemoteRoot/releases/$VersionName"
Ensure-RemoteDirectory -RemoteHost $RemoteHost -RemotePath $remoteVersionDir
& scp -q $LatestFile "${RemoteHost}:$($RemoteRoot)/" | Out-Null
if ($LASTEXITCODE -ne 0) {
Fail "Не удалось скопировать latest APK на remote host: ${RemoteHost}:$RemoteRoot"
}
& scp -q $ManifestFile "${RemoteHost}:$($RemoteRoot)/" | Out-Null
if ($LASTEXITCODE -ne 0) {
Fail "Не удалось скопировать манифест на remote host: ${RemoteHost}:$RemoteRoot"
}
& scp -q $VersionFile "${RemoteHost}:$($remoteVersionDir)/" | Out-Null
if ($LASTEXITCODE -ne 0) {
Fail "Не удалось скопировать versioned APK на remote host: ${RemoteHost}:$remoteVersionDir"
}
Write-Host "SCP на ${RemoteHost}:${RemoteRoot} выполнен."
}
function Resolve-AbsoluteProjectPath([string]$path) {
if ([string]::IsNullOrWhiteSpace($path)) {
return $null
}
if (Test-Path $path) {
return (Resolve-Path $path).ProviderPath
}
return $null
}
$projectDir = Resolve-AbsoluteProjectPath $RepoRoot
if (-not $projectDir) {
Fail "RepoRoot не найден: $RepoRoot"
}
$androidProject = [System.IO.Path]::GetFullPath((Join-Path $projectDir "clients\android"))
if (-not (Test-Path $androidProject)) {
Fail "Не найден Android проект: $androidProject"
}
$sdkRoot = Resolve-AndroidSdk $AndroidHome
if (-not $sdkRoot) {
Fail "Android SDK не найден. Укажи -AndroidHome или установи SDK в C:\Android\Sdk."
}
$cmdlineTools = Resolve-SdkComponent $sdkRoot "cmdline-tools\latest"
if (-not $cmdlineTools) {
Fail "Android cmdline-tools не найдены. Установи Android SDK command-line tools."
}
if (-not $env:ANDROID_HOME -or $env:ANDROID_HOME -ne $sdkRoot) {
Write-Host "Устанавливаю переменные ANDROID_HOME/ANDROID_SDK_ROOT в текущей сессии: $sdkRoot"
$env:ANDROID_HOME = $sdkRoot
}
$env:ANDROID_SDK_ROOT = $sdkRoot
$gradleBinary = Get-GradleBinary
$javaMajor = Get-JavaVersion
if ($javaMajor -ne "unknown" -and [int]$javaMajor -lt 17) {
Fail "Найден Java $javaMajor, нужен JDK 17+"
}
$versionInfo = Get-AndroidVersionInfo $androidProject
$versionName = $versionInfo.VersionName
if ([string]::IsNullOrWhiteSpace($versionName)) {
$versionName = "0"
}
$versionCode = $versionInfo.VersionCode
if ($versionCode -le 0) {
$versionCode = 0
}
$buildTypeNormalized = $BuildType.Trim().ToLower()
if ($buildTypeNormalized -notin @("debug","release")) {
Fail "BuildType должен быть debug или release"
}
if (-not $PrintOnly -and $buildTypeNormalized -eq "release" -and -not $PublishToTestDockerDownloads) {
Fail "Release сборка запрещена без публикации артефактов. Передай -PublishToTestDockerDownloads:`$true или запускай release-android-apk.ps1/fast-release-android-apk.ps1."
}
$buildTask = "assemble" + (Get-Culture).TextInfo.ToTitleCase($buildTypeNormalized)
Write-Host "=== RAP Android сборка APK ==="
Write-Host "Проект: $androidProject"
Write-Host "Версия: $versionName ($versionCode)"
Write-Host "SDK: $sdkRoot"
Write-Host "Gradle: $gradleBinary"
Write-Host "Build: $buildTask"
Write-Host "Build task: $buildTask"
if (-not (Test-Path (Join-Path $sdkRoot "platforms\android-35"))) {
Write-Host "Внимание: не найдено платформы android-35 в $sdkRoot\platforms. Проверь install: platforms;android-35."
}
if (-not (Test-Path (Join-Path $sdkRoot "build-tools\35.0.1"))) {
Write-Host "Внимание: не найдено build-tools 35.0.1 в $sdkRoot\build-tools."
}
$workspace = Join-Path $env:TEMP ("rap-android-build-" + [guid]::NewGuid().ToString("N"))
if (-not (Test-Path "C:\\")) {
Write-Host "Каталог C:\ недоступен. Использую TEMP: $env:TEMP"
} else {
$candidateWorkspace = Join-Path "C:\" ("rap-android-build-" + [guid]::NewGuid().ToString("N"))
$candidateParent = Split-Path $candidateWorkspace -Parent
if (Test-Path $candidateParent) {
$workspace = $candidateWorkspace
}
}
New-Item -ItemType Directory -Path $workspace -Force | Out-Null
Write-Host "Временная рабочая папка: $workspace"
$buildSucceeded = $false
$builtApk = $null
$summary = $null
try {
Write-Host "Копирую проект в локальную рабочую папку (избавление от проблем UNC)."
Copy-Item -Path (Join-Path $androidProject "*") -Destination $workspace -Recurse -Force
if ($PrintOnly) {
Write-Host "PrintOnly: сборка не выполнена."
$global:LASTEXITCODE = 0
return
}
$originalLocation = Get-Location
try {
Push-Location $workspace
$resolvedLocalProperties = Join-Path $workspace "local.properties"
if (Test-Path $resolvedLocalProperties) {
Remove-Item -Force $resolvedLocalProperties
}
Write-Host "Запускаю сборку..."
& $gradleBinary $buildTask
if ($LASTEXITCODE -ne 0) {
Fail "Gradle сборка завершилась с кодом $LASTEXITCODE"
}
}
finally {
Set-Location $originalLocation
}
$builtApkDir = Join-Path $workspace "app\build\outputs\apk\$buildTypeNormalized"
$candidateApks = @()
if (Test-Path $builtApkDir) {
$candidateApks = Get-ChildItem -Path $builtApkDir -Filter "app-${buildTypeNormalized}*.apk" -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -ExpandProperty FullName
}
if (-not $candidateApks -or $candidateApks.Count -eq 0) {
$fallbackApk = Join-Path $builtApkDir "app-$buildTypeNormalized.apk"
if (Test-Path $fallbackApk) {
$candidateApks = @($fallbackApk)
} else {
$candidateApks = @(Get-ChildItem -Path (Join-Path $workspace "app\build\outputs\apk") -Recurse -Filter "app-*.apk" -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -ExpandProperty FullName)
}
}
$builtApk = $candidateApks | Select-Object -First 1
if (-not $builtApk) {
$expectedPath = Join-Path $builtApkDir "app-${buildTypeNormalized}.apk"
Fail "Сборка не дала APK: $expectedPath"
}
$buildSucceeded = $true
$latestFileName = "rap-android-vpn-latest-$buildTypeNormalized.apk"
$versionFileName = "rap-android-vpn-$versionName-$buildTypeNormalized.apk"
$publishedPathPrefix = "downloads"
$publishDirs = @(
[System.IO.Path]::GetFullPath((Join-Path $projectDir "dist\downloads")),
[System.IO.Path]::GetFullPath((Join-Path $projectDir "web-admin\deploy\html\downloads"))
)
$publishResults = @()
foreach ($publishDir in $publishDirs) {
$result = Publish-Artifact -SourcePath $builtApk `
-PublishRoot $publishDir `
-BuildTypeNormalized $buildTypeNormalized `
-VersionName $versionName `
-VersionFileName $versionFileName `
-LatestFileName $latestFileName `
-PublishedPathPrefix $publishedPathPrefix `
-VersionTag $versionCode
$publishResults += $result
Write-Host "Опубликован в: $publishDir"
}
$summary = $publishResults[0]
if ($PublishToTestDockerDownloads) {
$remotePublishSource = if ($publishResults.Count -ge 2) { $publishResults[1] } else { $summary }
Write-Host "Публикую артефакты в test-docker ($TestDockerSshAlias): $TestDockerDownloadPath"
Publish-ToTestDockerDownloads -RemoteHost $TestDockerSshAlias -RemoteRoot (Resolve-RemoteDirectory $TestDockerDownloadPath) `
-VersionName $versionName `
-LatestFile $remotePublishSource.latest `
-VersionFile $remotePublishSource.versioned `
-ManifestFile $remotePublishSource.summary
if (-not [string]::IsNullOrWhiteSpace($TestDockerBackendReleasePath) -and $TestDockerBackendReleasePath -ne $TestDockerDownloadPath) {
Write-Host "Публикую артефакты в backend RAP_RELEASE_DIR ($TestDockerSshAlias): $TestDockerBackendReleasePath"
Publish-ToTestDockerDownloads -RemoteHost $TestDockerSshAlias -RemoteRoot (Resolve-RemoteDirectory $TestDockerBackendReleasePath) `
-VersionName $versionName `
-LatestFile $remotePublishSource.latest `
-VersionFile $remotePublishSource.versioned `
-ManifestFile $remotePublishSource.summary
}
}
Write-Host "Сборка завершена."
Write-Host "APK: $($summary.latest)"
Write-Host "Версионная копия: $($summary.versioned)"
Write-Host "Размер: $($summary.size) байт"
Write-Host "SHA256: $($summary.hash)"
}
finally {
if (-not $SkipWorkspaceCleanup -and (Test-Path $workspace)) {
Remove-Item -Recurse -Force $workspace
}
}
if (-not $buildSucceeded) {
Fail "Сборка не завершена."
}
$global:LASTEXITCODE = 0