#!/usr/bin/env bash # # Type-check and lint punktfunk's WINDOWS-only Rust from a Linux dev box. # # scripts/wincheck.sh # clippy -D warnings, the default # scripts/wincheck.sh check # cargo check instead # scripts/wincheck.sh clippy --fix # extra args are passed through to cargo # # Linux CI never compiles `#[cfg(target_os = "windows")]` code, so a Windows-side edit is otherwise # unverifiable until the Windows CI job runs (minutes) or someone tests on a real box. This gets the # same answer in ~1 s warm, against the WORKING TREE — the generated workspace symlinks each `src` # at the real crate directory, so there is nothing to keep in sync and no copies to go stale. # # WHY A SEPARATE WORKSPACE — the obvious in-tree command # # cargo check --target x86_64-pc-windows-msvc -p pf-capture # # fails, and NOT because of the Windows code: it dies in build scripts that compile C for the # target. `audiopus_sys` first (cmake wants a Visual Studio generator), then `ring` (needs an MSVC C # compiler plus the Windows SDK headers; clang-cl and lld-link exist locally but there is no SDK). # Both enter the graph through `punktfunk-core`'s `quic` feature. Stubbing opus with a fake # `opus.pc` gets past audiopus but not ring. # # The whole Windows capture stack uses exactly TWO items from punktfunk-core — `quic::HdrMeta` and # `Mode`, both plain data (re-verify with: # `grep -rho 'punktfunk_core::[A-Za-z_:]*' crates/pf-capture/src crates/pf-frame/src crates/pf-win-display/src | sort -u`). # So this script generates a ~30-line stub core carrying just those, and rustls/ring/opus never # enter the graph at all. Every path dep that is NOT a generated member points at the REAL crate by # absolute path, so their own relative deps and `version.workspace` inheritance still resolve. # # Note: `cargo fmt` needs none of this — rustfmt follows `mod`/`#[path]` without evaluating cfg, so # it already reaches Windows-only files. Mechanical edits can be normalised with plain `cargo fmt`. set -euo pipefail REPO="$(cd "$(dirname "$(readlink -f "$0")")/.." && pwd)" OUT="${WINCHECK_DIR:-$REPO/target/wincheck}" TARGET=x86_64-pc-windows-msvc # The generated members: crates whose Windows code we lint, plus the stub core they share. A path # dep naming one of these must stay RELATIVE (resolving to the generated member); anything else is # rewritten to the real crate. Getting that backwards silently drags the real punktfunk-core — and # with it ring and opus — back in through pf-frame, which is the entire thing this avoids. MEMBERS_RE='punktfunk-core|pf-frame|pf-win-display|pf-capture' REAL_MEMBERS=(pf-frame pf-win-display pf-capture) cmd="${1:-clippy}" [ $# -gt 0 ] && shift case "$cmd" in check | clippy) ;; *) echo "usage: $(basename "$0") [check|clippy] [extra cargo args...]" >&2 exit 2 ;; esac if ! rustc --print target-list | grep -qx "$TARGET"; then echo "wincheck: rustc does not know $TARGET" >&2 exit 1 fi if ! rustup target list --installed 2>/dev/null | grep -qx "$TARGET"; then echo "wincheck: target $TARGET not installed for the active toolchain — run:" >&2 echo " rustup target add $TARGET" >&2 exit 1 fi rm -rf "$OUT" mkdir -p "$OUT" # Mirror the real [workspace.package] so the members' `version.workspace = true` resolves. { echo '# GENERATED by scripts/wincheck.sh — do not edit; re-run the script.' echo '[workspace]' echo 'resolver = "2"' echo 'members = ["punktfunk-core", "pf-frame", "pf-win-display", "pf-capture"]' echo echo '[workspace.package]' sed -n '/^\[workspace\.package\]/,/^$/p' "$REPO/Cargo.toml" | sed '1d;/^$/d' } > "$OUT/Cargo.toml" mkdir -p "$OUT/punktfunk-core/src" cat > "$OUT/punktfunk-core/Cargo.toml" <<'EOF' # GENERATED by scripts/wincheck.sh — a STUB, not the real crate. [package] name = "punktfunk-core" version.workspace = true edition = "2021" rust-version.workspace = true [features] default = [] # Present so dependents' `features = ["quic"]` resolves; carries no quinn/rustls/opus. quic = [] EOF # Field layout and derives must match the real definitions, or the cross-check goes stale exactly # where it matters (the capture code builds HdrMeta literally and compares Modes). cat > "$OUT/punktfunk-core/src/lib.rs" <<'EOF' //! GENERATED by scripts/wincheck.sh — a STUB of punktfunk-core carrying only the two items the //! Windows capture stack uses. Mirrors crates/punktfunk-core: `Mode` from config.rs, `HdrMeta` //! from quic/datagram.rs. If either grows a field, mirror it here. /// Mirrors `punktfunk_core::Mode`. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Mode { pub width: u32, pub height: u32, pub refresh_hz: u32, } pub mod quic { /// Mirrors `punktfunk_core::quic::HdrMeta`. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct HdrMeta { pub display_primaries: [[u16; 2]; 3], pub white_point: [u16; 2], pub max_display_mastering_luminance: u32, pub min_display_mastering_luminance: u32, pub max_cll: u16, pub max_fall: u16, } } EOF for name in "${REAL_MEMBERS[@]}"; do mkdir -p "$OUT/$name" # Park the member paths behind a sentinel first: a plain self-substitution would be a no-op and # the next rule would rewrite them to absolute along with everything else. { echo "# GENERATED by scripts/wincheck.sh from crates/$name/Cargo.toml — src/ is a symlink." sed -E "s#path = \"\.\./($MEMBERS_RE)\"#path = \"@@M@@\1\"#g; \ s#path = \"\.\./([A-Za-z0-9_-]+)\"#path = \"$REPO/crates/\1\"#g; \ s#path = \"@@M@@#path = \"../#g" "$REPO/crates/$name/Cargo.toml" } > "$OUT/$name/Cargo.toml" ln -sfn "$REPO/crates/$name/src" "$OUT/$name/src" done cd "$OUT" for name in "${REAL_MEMBERS[@]}"; do echo "=== $cmd $name ($TARGET) ===" if [ "$cmd" = clippy ]; then cargo clippy --target "$TARGET" -p "$name" --all-targets "$@" -- -D warnings else cargo check --target "$TARGET" -p "$name" --all-targets "$@" fi done