222 lines
7.8 KiB
PowerShell
222 lines
7.8 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
Shared build helpers for Proxy God / ProxyChainManager.
|
|
Dot-sourced by setup_and_build.ps1 and release_build.ps1 — do not run directly.
|
|
#>
|
|
|
|
Set-StrictMode -Version Latest
|
|
|
|
function Get-RepoRoot {
|
|
# This file lives in scripts/ — parent is the repository root.
|
|
return (Split-Path -Parent $PSScriptRoot)
|
|
}
|
|
|
|
function Resolve-PythonExe {
|
|
if (Get-Command py -ErrorAction SilentlyContinue) {
|
|
try {
|
|
$out = (& py -3 -c "import sys; print(sys.executable)" 2>$null).Trim()
|
|
if ($out -and (Test-Path -LiteralPath $out)) { return $out }
|
|
} catch { }
|
|
}
|
|
$p = Get-Command python.exe -ErrorAction SilentlyContinue
|
|
if ($p) { return $p.Source }
|
|
throw @(
|
|
"Python 3.10+ not found (tried 'py -3' and 'python'). Install, then re-run:",
|
|
" winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements",
|
|
"Or https://www.python.org/downloads/ (enable 'Add python.exe to PATH')."
|
|
) -join "`n"
|
|
}
|
|
|
|
function Get-GitCommit {
|
|
try {
|
|
$c = (git -C (Get-RepoRoot) rev-parse --short HEAD 2>$null).Trim()
|
|
if ($c) { return $c }
|
|
} catch { }
|
|
return "unknown"
|
|
}
|
|
|
|
function Get-ReleaseVersion {
|
|
param([string]$Override = "")
|
|
if ($Override) { return $Override.Trim() }
|
|
$root = Get-RepoRoot
|
|
try {
|
|
$tag = (git -C $root describe --tags --exact-match 2>$null).Trim()
|
|
if ($tag) { return $tag.TrimStart("v") }
|
|
} catch { }
|
|
try {
|
|
$desc = (git -C $root describe --tags --always --dirty 2>$null).Trim()
|
|
if ($desc) { return ($desc -replace '^v', '') }
|
|
} catch { }
|
|
$py = Resolve-PythonExe
|
|
$v = (& $py -c "from proxy_chain_manager import __version__; print(__version__)" 2>$null).Trim()
|
|
if ($v) { return $v }
|
|
return "0.0.0-dev"
|
|
}
|
|
|
|
function Install-BuildDependencies {
|
|
param([string]$PythonExe, [string]$Root)
|
|
Write-Host "`n== pip (upgrade) =="
|
|
& $PythonExe -m pip install --upgrade pip
|
|
Write-Host "`n== runtime dependencies =="
|
|
& $PythonExe -m pip install -r (Join-Path $Root "requirements.txt")
|
|
Write-Host "`n== build dependencies =="
|
|
& $PythonExe -m pip install -r (Join-Path $Root "dev-requirements.txt")
|
|
}
|
|
|
|
function Invoke-UnitTests {
|
|
param([string]$PythonExe, [string]$Root)
|
|
Write-Host "`n== compileall =="
|
|
& $PythonExe -m compileall -q (Join-Path $Root "proxy_chain_manager")
|
|
if ($LASTEXITCODE -ne 0) { throw "compileall failed (exit $LASTEXITCODE)" }
|
|
Write-Host "`n== unittest discover =="
|
|
Push-Location $Root
|
|
try {
|
|
& $PythonExe -m unittest discover -s tests -v
|
|
if ($LASTEXITCODE -ne 0) { throw "unittest failed (exit $LASTEXITCODE)" }
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
function Invoke-StageBundledGost {
|
|
param([string]$Root)
|
|
Write-Host "`n== Stage bundled GOST =="
|
|
$prep = Join-Path $Root "scripts\prepare_bundled_gost.ps1"
|
|
& powershell -NoProfile -ExecutionPolicy Bypass -File $prep
|
|
if ($LASTEXITCODE -ne 0) { throw "prepare_bundled_gost.ps1 failed (exit $LASTEXITCODE)" }
|
|
}
|
|
|
|
function Invoke-GenerateVersionInfo {
|
|
param(
|
|
[string]$PythonExe,
|
|
[string]$Root,
|
|
[string]$Version,
|
|
[string]$Commit = ""
|
|
)
|
|
Write-Host "`n== Version metadata ($Version) =="
|
|
$gen = Join-Path $Root "scripts\generate_version_info.py"
|
|
$out = Join-Path $Root "build\version_info.txt"
|
|
$args = @($gen, "--version", $Version, "--out", $out)
|
|
if ($Commit) { $args += @("--commit", $Commit) }
|
|
& $PythonExe @args
|
|
if ($LASTEXITCODE -ne 0) { throw "generate_version_info.py failed (exit $LASTEXITCODE)" }
|
|
}
|
|
|
|
function Invoke-PyInstallerBuild {
|
|
param([string]$PythonExe, [string]$Root)
|
|
Write-Host "`n== PyInstaller (ProxyChainManager.spec) =="
|
|
$spec = Join-Path $Root "ProxyChainManager.spec"
|
|
if (-not (Test-Path $spec)) { throw "Spec not found: $spec" }
|
|
& $PythonExe -m PyInstaller --noconfirm --clean $spec
|
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed (exit $LASTEXITCODE)" }
|
|
$exe = Join-Path $Root "dist\ProxyChainManager.exe"
|
|
if (-not (Test-Path $exe)) { throw "Build failed: missing $exe" }
|
|
return $exe
|
|
}
|
|
|
|
function Invoke-CodeSign {
|
|
param([string]$ExePath)
|
|
$cert = $env:SIGN_CERT_PATH
|
|
if (-not $cert) {
|
|
Write-Host " (signing skipped — set SIGN_CERT_PATH to a .pfx to Authenticode-sign)"
|
|
return
|
|
}
|
|
if (-not (Test-Path $cert)) { throw "SIGN_CERT_PATH not found: $cert" }
|
|
$signtool = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
|
|
if (-not (Test-Path $signtool)) {
|
|
$signtool = (Get-Command signtool.exe -ErrorAction SilentlyContinue).Source
|
|
}
|
|
if (-not $signtool) { throw "signtool.exe not found — install Windows SDK" }
|
|
Write-Host "`n== Authenticode sign =="
|
|
$args = @("sign", "/fd", "SHA256", "/f", $cert, "/tr", "http://timestamp.digicert.com", "/td", "SHA256")
|
|
if ($env:SIGN_CERT_PASSWORD) {
|
|
$args += @("/p", $env:SIGN_CERT_PASSWORD)
|
|
}
|
|
$args += $ExePath
|
|
& $signtool @args
|
|
if ($LASTEXITCODE -ne 0) { throw "signtool sign failed (exit $LASTEXITCODE)" }
|
|
}
|
|
|
|
function Write-ExeHashSidecar {
|
|
param([string]$ExePath)
|
|
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $ExePath).Hash.ToLower()
|
|
$sidecar = "$ExePath.sha256"
|
|
"$hash *$(Split-Path -Leaf $ExePath)" | Out-File -FilePath $sidecar -Encoding ascii -Force
|
|
Write-Host "SHA256: $hash -> $sidecar"
|
|
return @{ Hash = $hash; Sidecar = $sidecar }
|
|
}
|
|
|
|
function Write-Sbom {
|
|
param(
|
|
[string]$PythonExe,
|
|
[string]$OutDir,
|
|
[string]$Version
|
|
)
|
|
Write-Host "`n== SBOM =="
|
|
$freeze = Join-Path $OutDir "requirements-frozen.txt"
|
|
& $PythonExe -m pip freeze | Out-File -FilePath $freeze -Encoding utf8
|
|
$sbom = Join-Path $OutDir "SBOM.json"
|
|
$pkgs = @()
|
|
Get-Content $freeze | ForEach-Object {
|
|
if ($_ -match '^([^=]+)==(.+)$') {
|
|
$pkgs += @{ name = $Matches[1]; version = $Matches[2] }
|
|
}
|
|
}
|
|
$manifest = @{
|
|
product = "Proxy God"
|
|
version = $Version
|
|
generated = (Get-Date).ToUniversalTime().ToString("o")
|
|
python = (& $PythonExe -c "import sys; print(sys.version.split()[0])").Trim()
|
|
packages = $pkgs
|
|
}
|
|
($manifest | ConvertTo-Json -Depth 4) | Out-File -FilePath $sbom -Encoding utf8
|
|
Write-Host "Wrote $sbom and $freeze"
|
|
return @{ Sbom = $sbom; Freeze = $freeze }
|
|
}
|
|
|
|
function Write-ReleaseManifest {
|
|
param(
|
|
[string]$OutDir,
|
|
[string]$Version,
|
|
[string]$Commit,
|
|
[string]$ExePath,
|
|
[string]$Sha256,
|
|
[hashtable]$Extra = @{}
|
|
)
|
|
$manifest = @{
|
|
product = "Proxy God"
|
|
executable = "ProxyChainManager.exe"
|
|
version = $Version
|
|
git_commit = $Commit
|
|
built_at_utc = (Get-Date).ToUniversalTime().ToString("o")
|
|
platform = "windows-amd64"
|
|
sha256 = $Sha256
|
|
uac_admin = $true
|
|
bundled_gost = $true
|
|
pyinstaller = "6.10.0"
|
|
}
|
|
foreach ($k in $Extra.Keys) { $manifest[$k] = $Extra[$k] }
|
|
$path = Join-Path $OutDir "RELEASE_MANIFEST.json"
|
|
($manifest | ConvertTo-Json -Depth 4) | Out-File -FilePath $path -Encoding utf8
|
|
Write-Host "Wrote $path"
|
|
return $path
|
|
}
|
|
|
|
function New-ReleaseZip {
|
|
param(
|
|
[string]$ReleaseDir,
|
|
[string]$Version,
|
|
[string]$Root
|
|
)
|
|
$zipName = "ProxyGod-v$Version-windows-amd64.zip"
|
|
$zipPath = Join-Path $Root "releases\$zipName"
|
|
$releasesRoot = Split-Path $zipPath -Parent
|
|
if (-not (Test-Path $releasesRoot)) {
|
|
New-Item -ItemType Directory -Path $releasesRoot | Out-Null
|
|
}
|
|
if (Test-Path $zipPath) { Remove-Item -LiteralPath $zipPath -Force }
|
|
Compress-Archive -Path (Join-Path $ReleaseDir "*") -DestinationPath $zipPath -Force
|
|
Write-Host "Release zip: $zipPath"
|
|
return $zipPath
|
|
}
|