A stock publish shipped native debug symbols to players: libSkiaSharp.pdb (80 MB) and libHarfBuzzSharp.pdb (20 MB) from Avalonia's rendering packages were 100 MB of a 278 MB launcher payload. MSBuild's DebugType switches only govern our own managed symbols, not native .pdb files arriving as package runtime assets, so the payload build drops every .pdb before zipping. launcher-win-x64.zip 103.4 -> 77.4 MB, client 44.5 -> 43.6 MB. The launcher payload now also fits under GitHub's 100 MB per-file limit, though the feed stays on the Gitea-only dist branch to keep main's history clean. Also fixes a StrictMode crash in the lock-file warning: an empty git status result is null, not an empty array, so .Count threw at the end of a successful publish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
246 lines
9.8 KiB
PowerShell
246 lines
9.8 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Publishes the alpha distribution feed into the repo's /bin folder.
|
|
|
|
.DESCRIPTION
|
|
Builds self-contained client and launcher payloads, zips them, and writes
|
|
bin/manifest.json pointing at this repository's PUBLIC Gitea raw URLs.
|
|
Commit and push /bin afterwards and the launcher's "Check for updates"
|
|
finds the new build — no release server, no tokens, no CDN.
|
|
|
|
Payload contents (what the launcher expects to find at each zip root):
|
|
client-<rid>.zip AcDream.App[.exe] + acdream-headless[.exe]
|
|
launcher-<rid>.zip acdream-launcher[.exe] + acdream-bake[.exe]
|
|
(the launcher csproj co-deploys the bake CLI)
|
|
|
|
The launcher's own published version is stamped to the manifest version so
|
|
a freshly published launcher does NOT report itself as out of date.
|
|
|
|
.PARAMETER Version
|
|
SemVer 2.0 release version. Defaults to a monotonic UTC build stamp,
|
|
e.g. 0.1.0-build.202608181552. Must sort ABOVE the previous published
|
|
version or the launcher will not offer it as an update.
|
|
|
|
.PARAMETER IncludeLinux
|
|
Also publish linux-x64 payloads. Off by default: Linux graphical is parked
|
|
at Slice L1, and each RID roughly doubles build time and feed size.
|
|
|
|
.EXAMPLE
|
|
pwsh -NoProfile -File tools/publish-bin.ps1
|
|
git add bin
|
|
git commit -m "release: alpha build"
|
|
git push origin main
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Version,
|
|
[switch]$IncludeLinux,
|
|
[string]$MinimumLauncherVersion = '0.0.1'
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if ($PSVersionTable.PSVersion.Major -lt 7) {
|
|
throw 'publish-bin requires PowerShell 7 or newer.'
|
|
}
|
|
|
|
$RepoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
if (-not (Test-Path (Join-Path $RepoRoot 'AcDream.slnx'))) {
|
|
throw "Could not locate AcDream.slnx above '$PSScriptRoot'."
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Version)) {
|
|
$Version = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm'))
|
|
}
|
|
|
|
$semver = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$'
|
|
foreach ($candidate in @($Version, $MinimumLauncherVersion)) {
|
|
if ($candidate -notmatch $semver) {
|
|
throw "Version '$candidate' is not SemVer 2.0 (build metadata '+' is not allowed here)."
|
|
}
|
|
}
|
|
|
|
# Raw-file base for the PUBLIC Gitea repo's dist branch. Must agree with
|
|
# ReleaseManifestClient.ProductionManifestUri.
|
|
$RawBase = 'https://git.snakedesert.se/erik/acdream/raw/branch/dist/bin'
|
|
|
|
$BinRoot = Join-Path $RepoRoot 'bin'
|
|
$Staging = Join-Path $BinRoot 'payload'
|
|
$Rids = @('win-x64')
|
|
if ($IncludeLinux) { $Rids += 'linux-x64' }
|
|
|
|
Write-Host "acdream alpha feed" -ForegroundColor Cyan
|
|
Write-Host " version : $Version"
|
|
Write-Host " rids : $($Rids -join ', ')"
|
|
Write-Host " output : $BinRoot"
|
|
Write-Host ''
|
|
|
|
if (Test-Path $Staging) { Remove-Item -LiteralPath $Staging -Recurse -Force }
|
|
$null = New-Item -ItemType Directory -Path $Staging -Force
|
|
|
|
function Invoke-Publish {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Project,
|
|
[Parameter(Mandatory)][string]$Rid,
|
|
[Parameter(Mandatory)][string]$OutputDirectory,
|
|
[switch]$SingleFile
|
|
)
|
|
|
|
$arguments = @(
|
|
'publish', (Join-Path $RepoRoot $Project),
|
|
'-c', 'Release',
|
|
'-r', $Rid,
|
|
'--self-contained', 'true',
|
|
# InformationalVersion ONLY — never -p:Version. The launcher reads
|
|
# AssemblyInformationalVersion (App.GetLauncherVersion), while Version
|
|
# also rewrites project-reference versions inside the committed
|
|
# packages.<rid>.lock.json files, so stamping it churned every lock
|
|
# file with a throwaway build stamp (observed 2026-08-18).
|
|
"-p:InformationalVersion=$Version",
|
|
# No SourceLink '+<sha>' suffix: LauncherVersion parses this as SemVer.
|
|
'-p:IncludeSourceRevisionInInformationalVersion=false',
|
|
'-o', $OutputDirectory,
|
|
'--nologo'
|
|
)
|
|
if ($SingleFile) { $arguments += '-p:PublishSingleFile=true' }
|
|
|
|
& dotnet @arguments | Out-Null
|
|
if ($LASTEXITCODE) { throw "publish failed: $Project ($Rid)" }
|
|
}
|
|
|
|
function Remove-DebugSymbols {
|
|
param([Parameter(Mandatory)][string]$Directory)
|
|
|
|
# Players never load these, and the native ones are enormous: a stock
|
|
# Avalonia publish ships libSkiaSharp.pdb (80 MB) and
|
|
# libHarfBuzzSharp.pdb (20 MB), which alone were 100 MB of a 278 MB
|
|
# launcher payload (measured 2026-08-18). MSBuild's DebugType switches
|
|
# only govern OUR managed symbols, not the native .pdb files that arrive
|
|
# as package runtime assets, so drop them from the payload directly.
|
|
$symbols = @(Get-ChildItem -LiteralPath $Directory -Recurse -File -Filter *.pdb)
|
|
if ($symbols.Count -eq 0) { return }
|
|
$freed = ($symbols | Measure-Object -Property Length -Sum).Sum
|
|
$symbols | Remove-Item -Force
|
|
' stripped {0} debug symbol file(s), {1:N1} MB' -f $symbols.Count, ($freed / 1MB) |
|
|
Write-Host -ForegroundColor DarkGray
|
|
}
|
|
|
|
function New-PayloadZip {
|
|
param(
|
|
[Parameter(Mandatory)][string]$SourceDirectory,
|
|
[Parameter(Mandatory)][string]$ZipPath,
|
|
[Parameter(Mandatory)][string[]]$RequiredFiles
|
|
)
|
|
|
|
Remove-DebugSymbols $SourceDirectory
|
|
|
|
foreach ($required in $RequiredFiles) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $SourceDirectory $required))) {
|
|
throw "Payload '$SourceDirectory' is missing required file '$required'."
|
|
}
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $ZipPath) { Remove-Item -LiteralPath $ZipPath -Force }
|
|
# includeBaseDirectory:$false -> entries sit at the zip ROOT, which is where
|
|
# LauncherExecutableSet resolves the hosts after extraction.
|
|
[IO.Compression.ZipFile]::CreateFromDirectory(
|
|
$SourceDirectory,
|
|
$ZipPath,
|
|
[IO.Compression.CompressionLevel]::Optimal,
|
|
$false)
|
|
}
|
|
|
|
function Get-Artifact {
|
|
param(
|
|
[Parameter(Mandatory)][string]$ZipPath,
|
|
[Parameter(Mandatory)][string]$Url
|
|
)
|
|
|
|
$item = Get-Item -LiteralPath $ZipPath
|
|
return [ordered]@{
|
|
url = $Url
|
|
sha256 = (Get-FileHash -LiteralPath $ZipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
size = $item.Length
|
|
}
|
|
}
|
|
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
|
|
|
|
$clients = [ordered]@{}
|
|
$launchers = [ordered]@{}
|
|
|
|
foreach ($rid in $Rids) {
|
|
$suffix = if ($rid -like 'win-*') { '.exe' } else { '' }
|
|
$clientDirectory = Join-Path $Staging "client-$rid"
|
|
$launcherDirectory = Join-Path $Staging "launcher-$rid"
|
|
|
|
Write-Host "[$rid] publishing client (App + Headless)..." -ForegroundColor Yellow
|
|
Invoke-Publish 'src/AcDream.App/AcDream.App.csproj' $rid $clientDirectory
|
|
Invoke-Publish 'src/AcDream.Headless/AcDream.Headless.csproj' $rid $clientDirectory
|
|
|
|
# PublishSingleFile/SelfContained come from the launcher csproj itself, and
|
|
# its PublishCoDeployedBakeTool target adds acdream-bake as a SECOND
|
|
# self-contained single file in the same directory. Two independent .NET
|
|
# runtimes is deliberate (see that csproj's comments: a framework-dependent
|
|
# bake would scatter AcDream.Content assemblies into the launcher's output),
|
|
# so this payload is ~103 MB and CANNOT be pushed to GitHub, whose hard
|
|
# per-file limit is 100 MB. The alpha feed is Gitea-only by design.
|
|
Write-Host "[$rid] publishing launcher (+ co-deployed bake)..." -ForegroundColor Yellow
|
|
Invoke-Publish 'src/AcDream.Launcher/AcDream.Launcher.csproj' $rid $launcherDirectory
|
|
|
|
$clientZip = Join-Path $BinRoot "client-$rid.zip"
|
|
$launcherZip = Join-Path $BinRoot "launcher-$rid.zip"
|
|
|
|
Write-Host "[$rid] packing..." -ForegroundColor Yellow
|
|
New-PayloadZip $clientDirectory $clientZip @("AcDream.App$suffix", "acdream-headless$suffix")
|
|
New-PayloadZip $launcherDirectory $launcherZip @("acdream-launcher$suffix", "acdream-bake$suffix")
|
|
|
|
$clients[$rid] = Get-Artifact $clientZip "$RawBase/client-$rid.zip"
|
|
$launchers[$rid] = Get-Artifact $launcherZip "$RawBase/launcher-$rid.zip"
|
|
}
|
|
|
|
$manifest = [ordered]@{
|
|
schemaVersion = 1
|
|
version = $Version
|
|
minimumLauncherVersion = $MinimumLauncherVersion
|
|
clients = $clients
|
|
launchers = $launchers
|
|
}
|
|
|
|
$manifestPath = Join-Path $BinRoot 'manifest.json'
|
|
$json = $manifest | ConvertTo-Json -Depth 6
|
|
[IO.File]::WriteAllText($manifestPath, $json + "`n", [Text.UTF8Encoding]::new($false))
|
|
|
|
Remove-Item -LiteralPath $Staging -Recurse -Force
|
|
|
|
Write-Host ''
|
|
Write-Host 'Feed written:' -ForegroundColor Green
|
|
$total = 0L
|
|
foreach ($file in (Get-ChildItem -LiteralPath $BinRoot -File | Sort-Object Name)) {
|
|
$total += $file.Length
|
|
' {0,-26} {1,10:N1} MB' -f $file.Name, ($file.Length / 1MB) | Write-Host
|
|
}
|
|
' {0,-26} {1,10:N1} MB' -f 'TOTAL', ($total / 1MB) | Write-Host
|
|
# A RID-specific restore rewrites packages.<rid>.lock.json for the projects it
|
|
# touches, so a feed build can leave the working tree dirty even though nothing
|
|
# about the source changed. Report it rather than silently reverting: the files
|
|
# are the developer's, and a real dependency change must not be swallowed here.
|
|
$dirtyLocks = @(
|
|
@(& git -C $RepoRoot status --porcelain -- '*packages.*.lock.json' 2>$null) |
|
|
Where-Object { $_ }
|
|
)
|
|
if ($dirtyLocks.Count -gt 0) {
|
|
Write-Host ''
|
|
Write-Host 'Note: the RID restore modified these lock files:' -ForegroundColor Yellow
|
|
$dirtyLocks | ForEach-Object { " $($_.Trim())" | Write-Host }
|
|
Write-Host ' If you did not change dependencies, discard them:' -ForegroundColor DarkGray
|
|
Write-Host " git checkout -- '*packages.*.lock.json'" -ForegroundColor DarkGray
|
|
}
|
|
|
|
Write-Host ''
|
|
Write-Host 'Next — publish the feed to Gitea:' -ForegroundColor Cyan
|
|
Write-Host " pwsh -NoProfile -File tools/publish-dist.ps1"
|
|
Write-Host ''
|
|
Write-Host ' (bin/ is gitignored on purpose: publish-dist puts it on the' -ForegroundColor DarkGray
|
|
Write-Host ' Gitea-only dist branch, never on main / GitHub.)' -ForegroundColor DarkGray
|