#Requires -Version 5.1 # Does not build or launch applications. [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('x86_64-pc-windows-msvc', 'x86_64-pc-windows-gnullvm')] [string]$Target, [Parameter(Mandatory = $true)] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$')] [string]$Version, [Parameter(Mandatory = $true)] [string]$BinaryDirectory, [string]$LlvmMinGWRoot, [string]$BuildEvidence, [string]$Python = 'python', [string]$NoticeManifest = (Join-Path $PSScriptRoot 'windows-notices/manifest.json'), [string]$OutputRoot = (Join-Path $PSScriptRoot '../dist') ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $repository = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) $utf8 = New-Object Text.UTF8Encoding($false) . (Join-Path $PSScriptRoot 'package-common.ps1') $windowsInputs = Get-PackageWindowsInputs $BinaryDirectory $Target $LlvmMinGWRoot @('arcadia-studio.exe', 'arcadia-server.exe') $inputs = $windowsInputs.Inputs $imports = $windowsInputs.Imports $inspectedHashes = $windowsInputs.InspectedHashes $binaryHashes = $windowsInputs.BinaryHashes $evidence = Read-PackageBuildEvidence $BuildEvidence $Target $binaryHashes $documentation = Get-PackageDocumentation $repository 'windows' $Python foreach ($name in $documentation.Keys) { $inputs[$name] = $documentation[$name] } # Resolve notice/source inputs before creating an output claim. No Cargo build occurs. . (Join-Path $PSScriptRoot 'windows-notices.ps1') $notices = Get-WindowsNotices $repository $Target $NoticeManifest $LlvmMinGWRoot foreach ($name in $notices.Inputs.Keys) { if ($inputs.Contains($name)) { throw "Notice collides with package input: $name" } $inputs[$name] = $notices.Inputs[$name] $inspectedHashes[$name] = $notices.Hashes[$name] } $output = [IO.Path]::GetFullPath($OutputRoot) $packageName = "ArcadiaStudio-$Version-$Target" $stage = Join-Path $output $packageName $archive = "$stage.zip" $checksum = "$archive.sha256" $lockPath = "$stage.packaging.lock" foreach ($path in @($stage, $archive, $checksum, $lockPath)) { if (Test-Path -LiteralPath $path) { throw "Output already exists; choose a fresh version or output root: $path" } } foreach ($directory in @('docs', 'examples', 'deployment')) { $sourceRoot = [IO.Path]::GetFullPath((Join-Path $repository $directory)) + [IO.Path]::DirectorySeparatorChar if (($stage + [IO.Path]::DirectorySeparatorChar).StartsWith($sourceRoot, [StringComparison]::OrdinalIgnoreCase)) { throw 'Output must not be inside a packaged source directory.' } } [void][IO.Directory]::CreateDirectory($output) $lock = [IO.File]::Open($lockPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) try { [void][IO.Directory]::CreateDirectory($stage) $inputHashes = [ordered]@{} foreach ($name in $inputs.Keys) { Assert-PackageOrdinaryFile $inputs[$name] $destination = Join-Path $stage $name [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) $before = (Get-FileHash -LiteralPath $inputs[$name] -Algorithm SHA256).Hash.ToLowerInvariant() if ($inspectedHashes.ContainsKey($name) -and $inspectedHashes[$name] -ne $before) { throw "Input changed after dependency/notice inspection: $name" } [IO.File]::Copy($inputs[$name], $destination, $false) if ((Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne $before) { throw "Input changed during staging: $name" } $inputHashes[$name] = $before } $metadata = [ordered]@{ version = $Version; target = $Target acceptance = 'Packaging integrity only; native clean-environment verification required.' build = 'Prebuilt inputs supplied by caller; this script does not establish build profile or source revision.' build_evidence = $evidence; imports = $imports; input_sha256 = $inputHashes } [IO.File]::WriteAllText((Join-Path $stage 'package.json'), ($metadata | ConvertTo-Json -Depth 14) + "`n", $utf8) Write-PackageNotices $stage $notices $start = @' Arcadia Studio portable Windows bundle Open arcadia-studio.exe. See docs/windows-packaging.md for runtime prerequisites and verification, README.md for model workflows, and docs/server.md to run the optional arcadia-server.exe. Examples are under examples/; Help is built in. Keep the bundled runtime DLL beside both executables when present. No compiler installation or machine PATH change is required by the staged runtime closure. This binary bundle does not include repository sources needed for Cargo/Docker build commands. Packaging integrity is not native release acceptance; read package.json and the distributor's binary-specific acceptance record for limitations. '@ [IO.File]::WriteAllText((Join-Path $stage 'START-HERE.txt'), $start + "`n", $utf8) $documentCheck = Invoke-PackageHelper $Python @('check-docs', '--stage', $stage) [IO.File]::WriteAllText((Join-Path $stage 'documentation-check.json'), ($documentCheck | ConvertTo-Json) + "`n", $utf8) $manifest = New-Object 'Collections.Generic.List[string]' foreach ($file in Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName) { $relative = $file.FullName.Substring($stage.Length + 1).Replace('\', '/') $manifest.Add((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + ' ' + $relative) } [IO.File]::WriteAllText((Join-Path $stage 'SHA256SUMS.txt'), ($manifest -join "`n") + "`n", $utf8) Add-Type -AssemblyName System.IO.Compression Add-Type -AssemblyName System.IO.Compression.FileSystem $zipStream = [IO.File]::Open($archive, [IO.FileMode]::CreateNew) $zip = New-Object IO.Compression.ZipArchive($zipStream, [IO.Compression.ZipArchiveMode]::Create) try { foreach ($file in Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName) { $relative = $file.FullName.Substring($stage.Length + 1).Replace('\', '/') $entry = $zip.CreateEntry("$packageName/$relative", [IO.Compression.CompressionLevel]::Optimal) $entry.LastWriteTime = [DateTimeOffset]::new(2000, 1, 1, 0, 0, 0, [TimeSpan]::Zero) $destination = $entry.Open() $source = $file.OpenRead() try { $source.CopyTo($destination) } finally { $source.Dispose(); $destination.Dispose() } } } finally { $zip.Dispose(); $zipStream.Dispose() } $zip = [IO.Compression.ZipFile]::OpenRead($archive) try { $seen = New-Object 'Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase) foreach ($entry in $zip.Entries) { if (-not $seen.Add($entry.FullName)) { throw "Duplicate archive entry: $($entry.FullName)" } $relative = $entry.FullName.Substring($packageName.Length + 1) $source = $entry.Open() $sha = [Security.Cryptography.SHA256]::Create() try { $actual = [BitConverter]::ToString($sha.ComputeHash($source)).Replace('-', '').ToLowerInvariant() } finally { $sha.Dispose(); $source.Dispose() } $expected = (Get-FileHash -LiteralPath (Join-Path $stage $relative) -Algorithm SHA256).Hash.ToLowerInvariant() if ($actual -ne $expected) { throw "Archive mismatch: $relative" } } if ($seen.Count -ne @(Get-ChildItem -LiteralPath $stage -Recurse -File).Count) { throw 'Archive file count mismatch.' } } finally { $zip.Dispose() } $archiveHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() [IO.File]::WriteAllText($checksum, "$archiveHash $packageName.zip`n", $utf8) [pscustomobject]@{ Directory = $stage; Archive = $archive; SHA256 = $archiveHash; Files = $seen.Count; NativeAcceptance = 'Pending' } } finally { $lock.Dispose() # Keep the claim file and any partial outputs: retries never overwrite an earlier attempt. }