186 lines
7.4 KiB
PowerShell
186 lines
7.4 KiB
PowerShell
param(
|
|
[string]$PythonExe = "C:\Python310\python.exe",
|
|
[switch]$SkipInstall
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Set-StrictMode -Version Latest
|
|
$env:PYTHONUTF8 = "1"
|
|
|
|
$clientRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
$venvRoot = Join-Path $clientRoot ".build-venv"
|
|
$buildRoot = Join-Path $clientRoot "build\client-package"
|
|
$releaseRoot = Join-Path $clientRoot "release"
|
|
$buildPython = Join-Path $venvRoot "Scripts\python.exe"
|
|
|
|
function Assert-ChildPath {
|
|
param([string]$Path, [string]$Parent)
|
|
|
|
$fullPath = [IO.Path]::GetFullPath($Path).TrimEnd('\')
|
|
$fullParent = [IO.Path]::GetFullPath($Parent).TrimEnd('\') + '\'
|
|
if (-not $fullPath.StartsWith($fullParent, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Refusing to operate outside the Client directory: $fullPath"
|
|
}
|
|
}
|
|
|
|
function Remove-BuildDirectory {
|
|
param([string]$Path)
|
|
|
|
Assert-ChildPath -Path $Path -Parent $clientRoot
|
|
if (Test-Path -LiteralPath $Path) {
|
|
Remove-Item -LiteralPath $Path -Recurse -Force
|
|
}
|
|
}
|
|
|
|
function Invoke-Checked {
|
|
param([scriptblock]$Command, [string]$FailureMessage)
|
|
|
|
& $Command
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "$FailureMessage (exit code: $LASTEXITCODE)"
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $PythonExe -PathType Leaf)) {
|
|
throw "Python 3.10 was not found: $PythonExe"
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $buildPython -PathType Leaf)) {
|
|
Write-Host "[1/7] Creating the isolated build environment..."
|
|
Invoke-Checked -FailureMessage "Failed to create the build environment" -Command {
|
|
& $PythonExe -m venv $venvRoot
|
|
}
|
|
}
|
|
|
|
if (-not $SkipInstall) {
|
|
Write-Host "[2/7] Installing pinned runtime and build dependencies..."
|
|
Invoke-Checked -FailureMessage "Failed to install build dependencies" -Command {
|
|
& $buildPython -m pip install --disable-pip-version-check `
|
|
-r (Join-Path $clientRoot "requirements.txt") `
|
|
-r (Join-Path $PSScriptRoot "requirements-build.txt")
|
|
}
|
|
} else {
|
|
Write-Host "[2/7] Skipping dependency installation."
|
|
}
|
|
|
|
Push-Location $clientRoot
|
|
try {
|
|
$version = (& $buildPython -c "from src.version import __version__; print(__version__)").Trim()
|
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($version)) {
|
|
throw "Failed to read the software version"
|
|
}
|
|
|
|
Write-Host "[3/7] Cleaning old build output..."
|
|
Remove-BuildDirectory -Path $buildRoot
|
|
Remove-BuildDirectory -Path $releaseRoot
|
|
New-Item -ItemType Directory -Force -Path $buildRoot, $releaseRoot | Out-Null
|
|
|
|
$versionParts = $version.Split('.')
|
|
if ($versionParts.Count -lt 3 -or $versionParts.Count -gt 4) {
|
|
throw "The version must contain three or four numeric parts: $version"
|
|
}
|
|
foreach ($part in $versionParts) {
|
|
$number = 0
|
|
if (-not [int]::TryParse($part, [ref]$number) -or $number -lt 0) {
|
|
throw "The version must contain three or four numeric parts: $version"
|
|
}
|
|
}
|
|
while ($versionParts.Count -lt 4) { $versionParts += "0" }
|
|
$fileVersion = $versionParts -join ','
|
|
$versionFile = Join-Path $buildRoot "windows-version.txt"
|
|
$versionInfo = @"
|
|
VSVersionInfo(
|
|
ffi=FixedFileInfo(filevers=($fileVersion), prodvers=($fileVersion),
|
|
mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1, subtype=0x0, date=(0, 0)),
|
|
kids=[StringFileInfo([StringTable('080404b0', [
|
|
StringStruct('CompanyName', 'CMAutoBuy'),
|
|
StringStruct('FileDescription', 'CMAutoBuy Client'),
|
|
StringStruct('FileVersion', '$version'),
|
|
StringStruct('InternalName', 'CMAutoBuy'),
|
|
StringStruct('OriginalFilename', 'CMAutoBuy.exe'),
|
|
StringStruct('ProductName', 'CMAutoBuy Client'),
|
|
StringStruct('ProductVersion', '$version')
|
|
])]), VarFileInfo([VarStruct('Translation', [2052, 1200])])]
|
|
)
|
|
"@
|
|
[IO.File]::WriteAllText($versionFile, $versionInfo, [Text.UTF8Encoding]::new($false))
|
|
|
|
$distPath = Join-Path $buildRoot "dist"
|
|
$workPath = Join-Path $buildRoot "work"
|
|
$specPath = Join-Path $buildRoot "spec"
|
|
|
|
Write-Host "[4/7] Building the main application..."
|
|
Invoke-Checked -FailureMessage "Failed to build the main application" -Command {
|
|
& $buildPython -m PyInstaller --noconfirm --clean --onedir --windowed `
|
|
--name CMAutoBuy --contents-directory dependencies `
|
|
--distpath $distPath --workpath $workPath --specpath $specPath `
|
|
--version-file $versionFile `
|
|
--collect-all qfluentwidgets `
|
|
--collect-all uiautomator2 `
|
|
--collect-all adbutils `
|
|
(Join-Path $clientRoot "buyer_main.py")
|
|
}
|
|
|
|
Write-Host "[5/7] Building the lightweight launcher..."
|
|
Invoke-Checked -FailureMessage "Failed to build the launcher" -Command {
|
|
& $buildPython -m PyInstaller --noconfirm --clean --onefile --windowed `
|
|
--name Launcher --distpath $distPath --workpath $workPath `
|
|
--specpath $specPath --version-file $versionFile `
|
|
(Join-Path $clientRoot "build_tools\launcher.py")
|
|
}
|
|
|
|
$mainDist = Join-Path $distPath "CMAutoBuy"
|
|
$launcherExe = Join-Path $distPath "Launcher.exe"
|
|
$adbExe = Join-Path $mainDist "dependencies\adbutils\binaries\adb.exe"
|
|
foreach ($requiredPath in @(
|
|
(Join-Path $mainDist "CMAutoBuy.exe"),
|
|
(Join-Path $mainDist "dependencies\PyQt5\Qt5\plugins\platforms\qwindows.dll"),
|
|
$adbExe,
|
|
$launcherExe
|
|
)) {
|
|
if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) {
|
|
throw "A required packaged file is missing: $requiredPath"
|
|
}
|
|
}
|
|
|
|
Write-Host "[6/7] Assembling portable and update archives..."
|
|
$packageName = "CMAutoBuy-$version"
|
|
$packageRoot = Join-Path $releaseRoot $packageName
|
|
$appRoot = Join-Path $packageRoot "app"
|
|
New-Item -ItemType Directory -Force -Path $packageRoot | Out-Null
|
|
Copy-Item -LiteralPath $mainDist -Destination $appRoot -Recurse
|
|
Copy-Item -LiteralPath $launcherExe -Destination (Join-Path $packageRoot "Launcher.exe")
|
|
New-Item -ItemType Directory -Force -Path `
|
|
(Join-Path $packageRoot "data"), `
|
|
(Join-Path $packageRoot "data\logs"), `
|
|
(Join-Path $packageRoot "data\artifacts") | Out-Null
|
|
[IO.File]::WriteAllText(
|
|
(Join-Path $appRoot "version.txt"),
|
|
"$version`n",
|
|
[Text.UTF8Encoding]::new($false)
|
|
)
|
|
|
|
$portableZip = Join-Path $releaseRoot "$packageName-portable.zip"
|
|
Compress-Archive -Path (Join-Path $packageRoot "*") -DestinationPath $portableZip -CompressionLevel Optimal
|
|
|
|
$updateStage = Join-Path $buildRoot "update-stage"
|
|
New-Item -ItemType Directory -Force -Path $updateStage | Out-Null
|
|
Copy-Item -LiteralPath $appRoot -Destination (Join-Path $updateStage "app") -Recurse
|
|
$updateZip = Join-Path $releaseRoot "$packageName-update.zip"
|
|
Compress-Archive -Path (Join-Path $updateStage "*") -DestinationPath $updateZip -CompressionLevel Optimal
|
|
|
|
Write-Host "[7/7] Generating the release manifest..."
|
|
$emDash = [char]0x2014
|
|
$manifestPath = Join-Path $releaseRoot ("autobuy{0}{0}manifest.json" -f $emDash)
|
|
Invoke-Checked -FailureMessage "Failed to generate the release manifest" -Command {
|
|
& $buildPython (Join-Path $clientRoot "build_tools\release_manifest.py") `
|
|
--version $version --update $updateZip --portable $portableZip `
|
|
--output $manifestPath
|
|
}
|
|
|
|
Write-Host "Build complete: $releaseRoot"
|
|
Write-Host "Release manifest: $manifestPath"
|
|
} finally {
|
|
Pop-Location
|
|
}
|