From bd8a0d80b04990ea84e616e005433e894fb8440c Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Fri, 21 Aug 2026 03:18:48 +0800 Subject: [PATCH] feat(native): package Windows sender evaluation build (#99) --- .github/workflows/native-sender-package.yml | 51 ++++++++ .../0006-fixed-high-native-sender-canary.md | 9 +- docs/adr/0008-window-scoped-audio-capture.md | 13 +- docs/project-memory.md | 6 +- docs/research/native-shared-encode-sender.md | 38 +++++- docs/status.md | 19 ++- native/process-audio-helper/README.md | 6 +- native/sender/README.md | 41 ++++++ native/sender/cmd/screener-sender/main.go | 16 +++ scripts/package-native-sender.ps1 | 122 ++++++++++++++++++ scripts/required-project-paths.txt | 2 + 11 files changed, 299 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/native-sender-package.yml create mode 100644 native/sender/README.md create mode 100644 scripts/package-native-sender.ps1 diff --git a/.github/workflows/native-sender-package.yml b/.github/workflows/native-sender-package.yml new file mode 100644 index 0000000..69af246 --- /dev/null +++ b/.github/workflows/native-sender-package.yml @@ -0,0 +1,51 @@ +name: Native sender package + +on: + workflow_dispatch: + pull_request: + paths: + - "native/sender/**" + - "native/process-audio-helper/**" + - "scripts/package-native-sender.ps1" + - ".github/workflows/native-sender-package.yml" + push: + branches: + - main + paths: + - "native/sender/**" + - "native/process-audio-helper/**" + - "scripts/package-native-sender.ps1" + - ".github/workflows/native-sender-package.yml" + +permissions: + contents: read + +jobs: + package: + runs-on: windows-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.26.6 + cache-dependency-path: native/sender/go.sum + - name: Build and verify package + shell: pwsh + run: | + ./scripts/package-native-sender.ps1 ` + -OutputDirectory (Join-Path $env:RUNNER_TEMP 'screener-native-package') ` + -Revision '${{ github.sha }}' + - name: Upload evaluation artifact + uses: actions/upload-artifact@v7 + with: + name: screener-native-windows-x64-${{ github.sha }} + path: | + ${{ runner.temp }}/screener-native-package/*.zip + ${{ runner.temp }}/screener-native-package/*.zip.sha256 + if-no-files-found: error + retention-days: 7 + compression-level: 0 diff --git a/docs/adr/0006-fixed-high-native-sender-canary.md b/docs/adr/0006-fixed-high-native-sender-canary.md index bd404b5..b327514 100644 --- a/docs/adr/0006-fixed-high-native-sender-canary.md +++ b/docs/adr/0006-fixed-high-native-sender-canary.md @@ -178,9 +178,12 @@ failed stage while synchronously retaining a bounded final-negative snapshot: candidates; a future selected-edge attempt requires its own generation-bound gate after SFU/UDP rather than widening or blocking the direct-path canary. -Only after these stages pass may separate performance, quality, loss, -reconnect, browser, audio/A-V-sync, packaging, and license gates begin. Do not -expand the feature to make the diagnostic gate pass. +A commit-bound, short-lived evaluation artifact may package the current source +to run these stages without a development toolchain. That artifact is not an +acceptance or release claim. Only after these stages pass may separate +performance, quality, loss, reconnect, browser, audio/A-V-sync, formal release, +and license gates begin. Do not expand the feature to make the diagnostic gate +pass. ## Consequences diff --git a/docs/adr/0008-window-scoped-audio-capture.md b/docs/adr/0008-window-scoped-audio-capture.md index 229da1b..e089d44 100644 --- a/docs/adr/0008-window-scoped-audio-capture.md +++ b/docs/adr/0008-window-scoped-audio-capture.md @@ -2,8 +2,9 @@ Date: 2026-08-21 -Status: Accepted. The browser hint and opt-in Native Windows P1 slice are -source-complete; Native remains unpackaged, default-off, and undeployed. +Status: Accepted. The browser hint, opt-in Native Windows P1 slice, and bounded +Windows x64 evaluation-package path are source-complete; Native remains +default-off, formally unreleased, and undeployed. ## Context @@ -122,12 +123,16 @@ track and 495 inbound Opus packets while video decoded/rendered 296 frames. privacy claim the platform cannot support. - Strict per-window audio is a native Windows 11 feature, not a browser promise or a silent system-audio fallback. +- The helper is packaged beside the sender in a commit-bound evaluation ZIP. + The ZIP carries internal and external SHA-256 manifests plus exact linked Go + dependency license files; it is neither an installer nor a release. - Android `AudioPlaybackCapture` and packaged cross-platform UI remain later candidates; they do not block this slice. ## Follow-Up TODO -- Package the helper beside the sender and keep explicit target selection - default-off. +- Download and run the short-lived Windows x64 evaluation artifact; keep + explicit target selection default-off. A formal release remains blocked on + the project license, signing, and its own acceptance boundary. - Keep native WGC video, a second Viewer, SFU/TURN, endurance, and real-game A/V sync as separate follow-ups. diff --git a/docs/project-memory.md b/docs/project-memory.md index 29d4f0c..90ec0fe 100644 --- a/docs/project-memory.md +++ b/docs/project-memory.md @@ -23,14 +23,14 @@ Last updated: 2026-08-21 - Use direct host P2P for one or two viewers, then the bounded controller for every room when `PEER_ASSISTED_MEDIA=true`. Room `1` is historical smoke, not a runtime gate; resource, quality, recovery, SFU/UDP, bounded-failure, and browser/mobile evidence remain separate. - Browser relays resend remote `MediaStreamTrack` values and re-encode at each hop; WebRTC does not guarantee a shared encoder across peer connections, so measure the cost. - Keep experiments bounded: the standard representation sequence is below; native RTP relay, encoded-object striping, and FEC remain separate. -- ADR-0006 remains no-go. Native H.264 opt-in loopback (Chrome 151, 1280x720) rendered 298/299 with zero fatal/encoder errors; VP8 remains default. Pion timing, hardware attribution, multi-viewer/endurance/public/packaged proof remain open. +- ADR-0006 remains no-go. VP8 is default; H.264 loopback rendered 298/299. Windows x64 evaluation packaging is source-complete; downloaded use, Pion timing, hardware, multi-viewer/endurance/public proof remain open. - Mobile Web Host is unsupported; feature-detect and fail clearly. Mobile Viewer stays leaf-only. After Windows native, gate Android 14+; iOS waits for stable iOS 27 ScreenCaptureKit. - ADR-0005 accepts direct/peer UDP, bounded SFU roots, then optional selected-edge TURN. Production now enables the one-shot selected-edge tuple after SFU; participant-wide TURN remains rejected and ordinary peer ICE stays STUN-only. No real TURN/SFU media canary has yet been run. - The controller is process-enabled, not room-allowlisted: `PEER_ASSISTED_MEDIA=true` gives every normal room the same direct/peer -> SFU -> selected-edge path with per-room state. Room `1` is historical smoke. Preserve sticky P2P, mobile leaves and break-before-make; recovery gets one attempt per layer (ICE restart, same-parent rebuild, alternate peer, then SFU), never three identical retries. - C+B quality reparenting is edge-local and cooldown-bound; no score, timer, or global parent penalty. - Flagship media is UDP; HTTPS/WSS stays TLS/TCP; the old release remains rollback-only. - Treat settings as ceilings and degradation as unclassified. Use correlated Host A+B/Viewer C and one-variable evidence; never force AV1, infer by UA, or create a composite score. -- Web capture cannot isolate process audio. ADR-0008 Windows 11 native audio is opt-in/source-only: opaque local selection, no system-mix fallback or exposed PID, and one shared Opus track; one direct Viewer received 495 packets. Packaging, game A/V sync, other routes, and Windows 10 remain open. +- Web cannot isolate process audio. ADR-0008 Win11 audio is opt-in/source-only with no system-mix fallback or exposed PID; one Viewer got 495 Opus packets. Evaluation packaging includes the helper, hashes, and linked licenses; release, game sync, other routes, and Win10 remain open. - Viewer-local names and the opt-in Web participant roster remain control-plane-only. Web Host names are deployed in the current release and stay socket/localStorage-only; Native remains outside the capability boundary. - Do not add scene detection, dynamic-FPS control, or forced AV1 without negotiation, encode, game, CPU/GPU, and sender evidence. - ADR-0002 grant/public access, the v3 room-password migration, and Web Host display names are deployed. Names/presence remain session-only without account or roster tables. @@ -53,7 +53,7 @@ Last updated: 2026-08-21 - Chrome 151 synthetic topology/quality runs kept fanout 2/1 and decoding; one relay close recovered in 5.32 seconds. Control evidence only. - Room `1` publishes `HIGH+LOW` with Dynacast off and subscriber `HIGH` ceilings, and now has bounded local quality reparenting. Both remain unverified on real media; test zero-child leaves and calibrated C+B loss before broad rollout. - C+B uses three hard-bad pairs, one-use samples and guarded intent/cooldown. Deployed admission rescue moves the oldest childless zero-capacity Host leaf below an unassigned one-slot relay in one revision; no score/timer/global rebalance. Deployment passed, but no real room triggered it. -- Web Host name/presence and the best-effort window-scoped audio request hint are deployed; both remain control-plane/capture hints and do not change the Native wire. Native H.264 opt-in source is present but no packaged native sender is in the release; VP8 remains the Web/production default. See `docs/research/native-h264-opt-in-path.md`. +- Web Host name/presence and window-audio hint are deployed without changing the Native wire. Native H.264 source and Windows x64 evaluation packaging are present, but no Native release/deployment exists; VP8 remains the Web/production default. See `docs/research/native-h264-opt-in-path.md`. ## Provisional Quality Targets diff --git a/docs/research/native-shared-encode-sender.md b/docs/research/native-shared-encode-sender.md index 0246c1b..c818f5f 100644 --- a/docs/research/native-shared-encode-sender.md +++ b/docs/research/native-shared-encode-sender.md @@ -477,6 +477,35 @@ static standard-path results and any future native reopen gate. This path still requires measured per-representation traffic/resources and cannot bypass #28's stock-GCC/RTX stop line. +## 2026-08-21 Evaluation Packaging Boundary + +The current source now has one Windows x64 evaluation-package path. It reuses +the existing Go build and MSVC helper build, places the two executables beside +each other, embeds the exact Git revision, adds an internal `SHA256SUMS.txt`, +and publishes the ZIP with an adjacent SHA-256 file. +`screener-sender.exe --version` is the non-interactive package smoke. This does not add an installer, +Electron, auto-update, signing, a bundled server, codec matrix, or release. + +The workflow uses the official `windows-latest` image, whose current manifest +includes VSWhere, the x64 Visual C++ tools, and Windows 11 SDK, and the official +immutable `actions/upload-artifact@v7` path with missing-file failure, zero +second-stage compression for the precompressed ZIP, and seven-day retention. +GitHub requires authentication to download the resulting artifact. The +workflow is path-filtered on pull requests and `main`, with manual dispatch for +an exact retained commit. Sources were checked 2026-08-21. + +The linked-binary audit covers 23 modules: coder/websocket is ISC; the 16 Pion +modules are MIT; google/uuid, wlynxg/anet, and the four `golang.org/x` modules +are BSD-style. Every linked module exposes a recognized root license file. The +packager derives the list from `go list -deps`, copies those exact files plus +the Go toolchain license, and fails if a linked module has no license file. No +GPL/AGPL reference or proprietary Oopz code enters the package. + +The project distribution license remains undecided. The Actions artifact is +therefore labeled evaluation-only and grants no project redistribution right; +GitHub Release publication remains blocked on the license decision. This +bounded trial artifact does not change ADR-0006's product No-Go status. + ## Product Stop Line The stock Pion GCC plus negotiated RFC 4588 RTX composition is @@ -544,10 +573,15 @@ SFU/UDP fails and needs its own bounded transport gate. - [Apple iOS ScreenCaptureKit sample](https://developer.apple.com/documentation/screencapturekit/capturing-screen-content-on-ios) - [Apple required hardware encoder key](https://developer.apple.com/documentation/videotoolbox/kvtvideoencoderspecification_requirehardwareacceleratedvideoencoder) - [Apple hardware encoder readback](https://developer.apple.com/documentation/videotoolbox/kvtcompressionpropertykey_usinghardwareacceleratedvideoencoder) +- [GitHub Actions artifact upload](https://github.com/actions/upload-artifact) +- [GitHub Windows 2025 runner image](https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md) +- [PowerShell `Compress-Archive`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive) +- [Go license](https://go.dev/LICENSE) The current Go candidate directly uses Pion (MIT) and coder/websocket (ISC). -Before distributing any sender executable, choose the project license and -generate and verify notices for those direct and all transitive dependencies. +The evaluation package generates and verifies notices for the Go runtime and +all linked direct/transitive dependencies. Choose the project license before a +formal release or redistribution grant. libwebrtc/H.264 remains a separate future route requiring its own third-party notice and patent/licensing review. No source code from these projects was copied into Screener. diff --git a/docs/status.md b/docs/status.md index 15e561e..6e8b61c 100644 --- a/docs/status.md +++ b/docs/status.md @@ -15,10 +15,9 @@ active/running with `NRestarts=0`, and local/public health are 200. Ordinary ICE remains STUN-only; `PEER_ASSISTED_MEDIA=true` enables the bounded peer/SFU-UDP controller for every room (at most two roots), while the selected-edge UDP tuple is configured with TTL 120. Room `1` is historical -smoke only. No real TURN/SFU media canary has run. Web Host names and the -best-effort window-scoped audio request hint are deployed; Native H.264 source is -available but no packaged native sender is deployed, and VP8 remains the Web -default. +smoke only. No real TURN/SFU media canary has run. Web Host names/audio hint are +deployed. Native H.264 and Windows x64 evaluation packaging are source-only; +there is no Native release/deployment, and Web defaults VP8. ## Execution Principle @@ -42,7 +41,7 @@ Flagship; parallel; min run/smoke/rollback; benchmark later. Active UI/config/lo - Host A+B and authenticated P2P Viewer C are sanitized, generation-bound, read-only, and fail closed for stale, ambiguous, or SFU-fed evidence; no raw media metadata is retained. - The `a11a73d` built-in TURN canary is rejected: local allocation passed, but direct Host/Pion Viewer failed before forced relay. Full rollback restored STUN-only client ICE and zero allocations. - Chrome 151 loopbacks decoded VP8 299 and H.264 opt-in 299/298 rendered at 1280x720 without fatal/encoder errors. Hardware attribution, Pion timing, multi-viewer/endurance/public/native proof remain open; production stays VP8. -- Native Win11 audio is source-only/default-off. Smoke isolated target 4018x; one Viewer got 495 Opus packets. Packaging, game sync, Win10, and other routes remain open. +- Native Win11 audio is source-only/default-off. Target isolation was 4018x; one Viewer got 495 Opus packets. Package build passes; download, game sync, Win10, and other routes are open. - Access protocol/config/HTTP/storage/SQLite/signaling focused tests pass, including commit-first teardown and v1 rollback. - Source adds selected Host ingress and peer last-mile for every enabled room; focused routing/config tests pass with Peer ICE STUN-only. - Host names, the window-audio hint, all-room routing, and all-room Host selected ingress are active; the stale restart assertion is historical only. @@ -54,7 +53,7 @@ Flagship; parallel; min run/smoke/rollback; benchmark later. Active UI/config/lo - Android Chrome/iOS Safari Viewer leaves remain unverified and conservatively leaf-only. Mobile Web Host is unsupported; native senders are planned only. - Silent partitions can wait 30 to 60 seconds for heartbeat detection before the default 5-second grace; this remains unverified. - The former release reportedly sustained bandwidth-limited blur on a capable LAN; Host refresh recovered while Viewer refresh did not. The new deployment has not yet reproduced or cleared it. -- ADR-0006 has one Viewer proof; two-edge/FIFO, physical hardware, endurance, public, packaged-native, and browser-diversity proof remain open. +- ADR-0006 has one Viewer proof; two-edge/FIFO, hardware, endurance, public/downloaded-package, and browser-diversity proof remain open. - Browser fanout is host two/viewer one; any accepted endpoint relay stays capped at two downstream edges. - PR #49/#50 BWE is unverified/default-off: test zero-child leaves, then root impact. No local per-leaf UDP shaper; resume with Linux `tc` or public canary, never CDP. Web P2P/SVC shortcuts remain no-go. - The corrected standby smoke is localhost/headless/video-only; public DNS/TLS reuse, transport, audio, shaping, load, mobile, endurance, and sub-25 ms overlap remain open. @@ -71,10 +70,10 @@ SFU; it never runs three identical retries or abandons progressing P2P early. Next gate exactly-two/Dynacast-off BWE on a zero-child SFU leaf, then root evacuation. Native source accepts increasing capture timestamps shorter than -duration; VP8 and explicit H.264 Viewer decode/render are recorded. Hardware -preference is not physical proof. Next prove fresh Pion -outbound diagnostics before viewer 2/FIFO/hardware/endurance/deploy. -Package Native audio and test one real-game A/V pair later without displacing P0. +duration; VP8 and H.264 Viewer decode/render are recorded. Preference is not +proof. Package one-Viewer VP8/H.264/Opus first; WGC/MF hardware then +Viewer2/FIFO are functional slices. The 2s Pion sample is later diagnostic +cleanup. Test real-game A/V later without displacing P0. Ordinary Peer ICE stays STUN-only; participant-wide TURN is removed. The selected-edge tuple is configured in production, but Host ingress still needs one forced-relay canary before any media-success claim. Retained performance/resource diff --git a/native/process-audio-helper/README.md b/native/process-audio-helper/README.md index d843bce..844c793 100644 --- a/native/process-audio-helper/README.md +++ b/native/process-audio-helper/README.md @@ -5,8 +5,10 @@ It enumerates visible top-level windows locally and captures only the explicitly selected process plus its children through WASAPI application loopback. It has no whole-system fallback and no network code. -Build it outside the repository, then place both executables in the same output -directory (or set `SCREENER_PROCESS_AUDIO_HELPER` for a development run): +The standard evaluation ZIP builds this helper beside the sender through +`scripts/package-native-sender.ps1`. For a helper-only development build, use +an output directory outside the repository (or set +`SCREENER_PROCESS_AUDIO_HELPER` for a development run): ```powershell $out = Join-Path ([IO.Path]::GetTempPath()) 'screener-native-audio' diff --git a/native/sender/README.md b/native/sender/README.md new file mode 100644 index 0000000..dec62d3 --- /dev/null +++ b/native/sender/README.md @@ -0,0 +1,41 @@ +# Screener Native Sender for Windows x64 + +This evaluation package contains the current Native sender and the Windows 11 +process-audio helper. It has no installer, updater, code signature, or bundled +server. + +## Run + +1. Keep `screener-sender.exe` and `screener-process-audio.exe` in the same + directory. +2. Run `screener-sender.exe`. It opens a loopback-only UI in the default + browser; current Chrome or Edge is required. +3. Enter the Screener server and Host admission password, choose VP8 (default) + or experimental H.264, then choose the screen or window to share. +4. Process audio is off by default. To enable it, select the exact target in + the audio list and select the same target in the browser picker. +5. Close the console window to stop the sender. + +Process audio requires Windows 11 build 22000 or newer. It captures only the +selected process tree and never falls back to the system mix. H.264 requests +browser hardware preference but does not prove which physical encoder Chrome +or Edge selected. Viewers continue using the normal Web client. + +Verify the downloaded ZIP against its adjacent `.sha256` file, then verify the +files inside it with `SHA256SUMS.txt`. The package is unsigned, so Windows may +show an unknown-publisher warning. + +The project distribution license is not yet selected. This short-lived Actions +artifact is for evaluation and does not grant redistribution rights. Exact +third-party license texts for the linked Go runtime and modules are under +`licenses/`. + +## Build + +From a clean Git checkout on Windows with Go 1.26.6 and Visual Studio C++ Build +Tools installed: + +```powershell +$output = Join-Path ([IO.Path]::GetTempPath()) "screener-native-package-$([Guid]::NewGuid())" +./scripts/package-native-sender.ps1 -OutputDirectory $output +``` diff --git a/native/sender/cmd/screener-sender/main.go b/native/sender/cmd/screener-sender/main.go index 2177cf6..aa29b80 100644 --- a/native/sender/cmd/screener-sender/main.go +++ b/native/sender/cmd/screener-sender/main.go @@ -12,7 +12,23 @@ import ( "github.com/TNTcraftHIM/Screener/native/sender/internal/app" ) +var buildRevision = "development" + func main() { + if len(os.Args) == 2 { + switch os.Args[1] { + case "--version": + fmt.Printf("Screener sender %s\n", buildRevision) + return + case "--help", "-h": + fmt.Println("Usage: screener-sender.exe [--version|--help]") + return + } + } + if len(os.Args) > 1 { + fmt.Fprintln(os.Stderr, "Usage: screener-sender.exe [--version|--help]") + os.Exit(2) + } if err := run(); err != nil { fmt.Fprintln(os.Stderr, "Screener sender stopped:", err) os.Exit(1) diff --git a/scripts/package-native-sender.ps1 b/scripts/package-native-sender.ps1 new file mode 100644 index 0000000..382e059 --- /dev/null +++ b/scripts/package-native-sender.ps1 @@ -0,0 +1,122 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$OutputDirectory, + + [string]$Revision +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path +$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $scriptDirectory '..')) +$outputRoot = [IO.Path]::GetFullPath($OutputDirectory) +$repositoryPrefix = $repositoryRoot.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar +if ($outputRoot.Equals($repositoryRoot, [StringComparison]::OrdinalIgnoreCase) -or + $outputRoot.StartsWith($repositoryPrefix, [StringComparison]::OrdinalIgnoreCase)) { + throw 'OutputDirectory must be outside the repository.' +} +if (Test-Path -LiteralPath $outputRoot) { + throw 'OutputDirectory must not already exist.' +} + +$headRevision = (& git -C $repositoryRoot rev-parse HEAD).Trim() +if ($LASTEXITCODE -ne 0) { throw 'Unable to resolve the Git revision.' } +if ([string]::IsNullOrWhiteSpace($Revision)) { $Revision = $headRevision } +$Revision = $Revision.Trim().ToLowerInvariant() +if ($Revision -notmatch '^[0-9a-f]{40}$' -or $Revision -ne $headRevision) { + throw 'Revision must be the full SHA-1 of the checked-out commit.' +} +$dirty = @(git -C $repositoryRoot status --porcelain) +if ($LASTEXITCODE -ne 0 -or $dirty.Count -ne 0) { + throw 'Native packages must be built from a clean Git checkout.' +} + +$go = Get-Command go.exe -ErrorAction Stop +$senderDirectory = Join-Path $repositoryRoot 'native\sender' +$packageName = "screener-native-windows-x64-$($Revision.Substring(0, 12))" +$packageDirectory = Join-Path $outputRoot $packageName +$licenseDirectory = Join-Path $packageDirectory 'licenses' +$senderExecutable = Join-Path $packageDirectory 'screener-sender.exe' +$zipPath = Join-Path $outputRoot "$packageName.zip" +New-Item -ItemType Directory -Path $licenseDirectory -Force | Out-Null + +$previousGoOS = $env:GOOS +$previousGoArch = $env:GOARCH +$previousCGO = $env:CGO_ENABLED +Push-Location $senderDirectory +try { + $env:GOOS = 'windows' + $env:GOARCH = 'amd64' + $env:CGO_ENABLED = '0' + $linkerFlags = "-X main.buildRevision=$Revision" + & $go.Source build -mod=readonly -trimpath -buildvcs=true -ldflags $linkerFlags -o $senderExecutable ./cmd/screener-sender + if ($LASTEXITCODE -ne 0) { throw 'Native sender build failed.' } + + $moduleLines = @(& $go.Source list -deps -f '{{with .Module}}{{if not .Main}}{{.Path}}|{{.Version}}|{{.Dir}}{{end}}{{end}}' ./cmd/screener-sender | + Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique) + if ($LASTEXITCODE -ne 0 -or $moduleLines.Count -eq 0) { + throw 'Unable to enumerate linked Go modules.' + } + $goVersion = (& $go.Source version).Trim() + $goRoot = (& $go.Source env GOROOT).Trim() +} finally { + Pop-Location + $env:GOOS = $previousGoOS + $env:GOARCH = $previousGoArch + $env:CGO_ENABLED = $previousCGO +} + +& (Join-Path $repositoryRoot 'native\process-audio-helper\build.ps1') -OutputDirectory $packageDirectory | Out-Null +$helperObject = Join-Path $packageDirectory 'main.obj' +if (Test-Path -LiteralPath $helperObject) { Remove-Item -LiteralPath $helperObject -Force } + +$moduleManifest = @() +foreach ($line in $moduleLines) { + $modulePath, $moduleVersion, $moduleDirectory = $line -split '\|', 3 + if ([string]::IsNullOrWhiteSpace($moduleDirectory) -or -not (Test-Path -LiteralPath $moduleDirectory -PathType Container)) { + throw "Linked module source is unavailable: $modulePath@$moduleVersion" + } + $licenseFiles = @(Get-ChildItem -LiteralPath $moduleDirectory -File | Where-Object { + $_.Name -match '^(LICENSE|LICENCE|COPYING|NOTICE)(\.|$)' + }) + if ($licenseFiles.Count -eq 0) { throw "Linked module has no recognized license file: $modulePath@$moduleVersion" } + $moduleLicenseDirectory = Join-Path $licenseDirectory (($modulePath + '@' + $moduleVersion) -replace '[^A-Za-z0-9._@+-]', '_') + New-Item -ItemType Directory -Path $moduleLicenseDirectory | Out-Null + $licenseFiles | Copy-Item -Destination $moduleLicenseDirectory + $moduleManifest += "$modulePath $moduleVersion" +} + +$goLicense = Join-Path $goRoot 'LICENSE' +if (-not (Test-Path -LiteralPath $goLicense -PathType Leaf)) { throw 'Go toolchain license was not found.' } +$goLicenseDirectory = Join-Path $licenseDirectory 'go-toolchain' +New-Item -ItemType Directory -Path $goLicenseDirectory | Out-Null +Copy-Item -LiteralPath $goLicense -Destination (Join-Path $goLicenseDirectory 'LICENSE') +$moduleManifest | Set-Content -LiteralPath (Join-Path $licenseDirectory 'MODULES.txt') -Encoding ascii +Copy-Item -LiteralPath (Join-Path $senderDirectory 'README.md') -Destination (Join-Path $packageDirectory 'README.md') +@( + "revision=$Revision" + 'platform=windows/amd64' + "toolchain=$goVersion" +) | Set-Content -LiteralPath (Join-Path $packageDirectory 'REVISION.txt') -Encoding ascii + +$versionOutput = (& $senderExecutable --version).Trim() +if ($LASTEXITCODE -ne 0 -or $versionOutput -ne "Screener sender $Revision") { + throw 'Packaged sender revision smoke failed.' +} + +$packagePrefix = $packageDirectory.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar +$checksums = Get-ChildItem -LiteralPath $packageDirectory -File -Recurse | Sort-Object FullName | ForEach-Object { + $relativePath = $_.FullName.Substring($packagePrefix.Length).Replace('\', '/') + "$((Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()) $relativePath" +} +$checksums | Set-Content -LiteralPath (Join-Path $packageDirectory 'SHA256SUMS.txt') -Encoding ascii + +Compress-Archive -LiteralPath $packageDirectory -DestinationPath $zipPath -CompressionLevel Optimal +$zipHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant() +"$zipHash $([IO.Path]::GetFileName($zipPath))" | + Set-Content -LiteralPath "$zipPath.sha256" -Encoding ascii + +Write-Output $zipPath +Write-Output "$zipPath.sha256" diff --git a/scripts/required-project-paths.txt b/scripts/required-project-paths.txt index 3226192..fcab6b5 100644 --- a/scripts/required-project-paths.txt +++ b/scripts/required-project-paths.txt @@ -14,6 +14,7 @@ tsconfig.json .githooks/pre-commit .github/pull_request_template.md .github/workflows/repository-hygiene.yml +.github/workflows/native-sender-package.yml docs/README.md docs/project-memory.md docs/status.md @@ -27,6 +28,7 @@ docs/research/webrtc-p2p-screen-sharing.md scripts/required-project-paths.txt scripts/check-project-state.sh scripts/check-project-state.ps1 +scripts/package-native-sender.ps1 scripts/install-hooks.sh scripts/install-hooks.ps1 deploy/coturn/turnserver.conf.example