Record project continuation changes
This commit is contained in:
@@ -0,0 +1,403 @@
|
||||
#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"
|
||||
)
|
||||
|
||||
$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+."
|
||||
}
|
||||
|
||||
$versionLine = & $javaCandidate.Source -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-rdp-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"
|
||||
}
|
||||
}
|
||||
|
||||
$published | ConvertTo-Json -Depth 6 | Out-File -FilePath $metaPath -Encoding utf8 -Force
|
||||
|
||||
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: сборка не выполнена."
|
||||
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-rdp-vpn-latest-$buildTypeNormalized.apk"
|
||||
$versionFileName = "rap-android-rdp-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
|
||||
}
|
||||
|
||||
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 "Сборка не завершена."
|
||||
}
|
||||
Reference in New Issue
Block a user