From 6c23978aa57e810dd99f7e4f2ff682045b399ef3 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 1 Jun 2026 10:10:11 -0700 Subject: [PATCH 1/2] go: shrink moq-ffi staticlib with LTO so the mirror push fits under GitHub's 100 MB limit The moq-go mirror has been stuck at v0.2.15 since #1549. That PR correctly fixed go/scripts/package.sh to stage moq.h and the Linux staticlibs, but it traded "missing files" for "files too big to push": the unstripped Linux libmoq_ffi.a is ~110 MB, and GitHub's pre-receive hook hard-rejects any file over 100 MB (GH001). Every release push since (v0.2.16, v0.2.17) failed at the publish step, so `go get github.com/moq-dev/moq-go@latest` still resolves to the pre-fix v0.2.15 and consumers' cgo builds fail on the missing header. Enable thin LTO with a single codegen unit for the release artifact in rs/moq-ffi/build.sh (the shared build script for the Go/Swift/Kotlin releases). LTO dead-strips the unused monomorphizations Rust bakes into a staticlib, cutting it ~60% (76 MB -> 28 MB on darwin, ~110 MB -> ~40 MB on Linux) with no source or ABI changes. Verified the full uniffi C export set is preserved and that `go vet`/`build`/`test` link cleanly against the LTO'd lib. Scoped via env vars so a plain `cargo build --release` stays fast. Also add a size guard to go/scripts/package.sh that fails the package step with a file-named error if any staged lib hits 100 MiB, so a future regression fails fast with context instead of dying at `git push` after an hour-long multi-target build. Co-Authored-By: Claude Opus 4.8 (1M context) --- go/scripts/package.sh | 17 ++++++++++++++++- rs/moq-ffi/build.sh | 13 +++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/go/scripts/package.sh b/go/scripts/package.sh index 0627bcefa..6585ae735 100755 --- a/go/scripts/package.sh +++ b/go/scripts/package.sh @@ -131,7 +131,13 @@ GO_LIBS=( "aarch64-apple-darwin:darwin_arm64:libmoq_ffi.a" "x86_64-pc-windows-msvc:windows_amd64:moq_ffi.lib" ) +# GitHub's pre-receive hook rejects any file >= 100 MiB. The mirror commits +# these libs into git, so an oversized lib fails the publish push (GH001). +# Catch it here, where the error names the file and points at the fix, rather +# than after a multi-target build burns an hour to die at `git push`. +GITHUB_FILE_LIMIT=$((100 * 1024 * 1024)) STAGED_ANY=false +OVERSIZED=() for entry in "${GO_LIBS[@]}"; do target="${entry%%:*}" rest="${entry#*:}" @@ -142,7 +148,9 @@ for entry in "${GO_LIBS[@]}"; do dest="$PKG_STAGE/moq/lib/$goarch" mkdir -p "$dest" cp "$src" "$dest/" - echo " go lib $goarch <- $target" + size=$(wc -c <"$src") + echo " go lib $goarch <- $target ($((size / 1024 / 1024)) MiB)" + [[ "$size" -ge "$GITHUB_FILE_LIMIT" ]] && OVERSIZED+=("$goarch/$libname: $((size / 1024 / 1024)) MiB") STAGED_ANY=true else echo " go lib $goarch: skipped, $src missing" @@ -154,6 +162,13 @@ if [[ "$STAGED_ANY" != true ]]; then exit 1 fi +if [[ "${#OVERSIZED[@]}" -gt 0 ]]; then + echo "Error: staged libs exceed GitHub's 100 MiB push limit:" >&2 + printf ' %s\n' "${OVERSIZED[@]}" >&2 + echo "The mirror push would be rejected (GH001). Shrink the staticlib in rs/moq-ffi/build.sh (LTO)." >&2 + exit 1 +fi + # --- 4. Minimal consumer-facing README rewrite --- # The full developer README lives in the monorepo; the staged copy # (which ends up on moq-dev/moq-go) gets a thin orientation pointer. diff --git a/rs/moq-ffi/build.sh b/rs/moq-ffi/build.sh index 37f00ee32..171f204d4 100755 --- a/rs/moq-ffi/build.sh +++ b/rs/moq-ffi/build.sh @@ -14,6 +14,19 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" WORKSPACE_DIR="$(cd "$RS_DIR/.." && pwd)" +# Shrink the release staticlib. The Go mirror commits the native libs straight +# into git, and GitHub's pre-receive hook hard-rejects any file over 100 MB +# (GH001). An unstripped moq-ffi staticlib is ~110 MB on Linux, which silently +# blocked every moq-go mirror push and pinned consumers to a stale release. +# Thin LTO with a single codegen unit dead-strips the unused monomorphizations +# Rust bakes into a staticlib, cutting it by ~60% (~110 MB to ~35 MB) with no +# source or ABI changes. (Swift ships an XCFramework release asset and Kotlin a +# Maven .so, so neither hits the git limit, but both still get smaller libs.) +# Scoped here via env vars so a plain `cargo build --release` stays fast; a +# caller can still override. +export CARGO_PROFILE_RELEASE_LTO="${CARGO_PROFILE_RELEASE_LTO:-thin}" +export CARGO_PROFILE_RELEASE_CODEGEN_UNITS="${CARGO_PROFILE_RELEASE_CODEGEN_UNITS:-1}" + # Resolve cargo target directory (respects CARGO_TARGET_DIR, .cargo/config, etc.) TARGET_BASE_DIR=$(cargo metadata --format-version 1 --manifest-path "$WORKSPACE_DIR/Cargo.toml" --no-deps 2>/dev/null | sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p' || From c499809d4c043f33c45d7b58e20164b3bd5e85c5 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 1 Jun 2026 10:23:22 -0700 Subject: [PATCH 2/2] libmoq: shrink the staticlib with LTO to match moq-ffi libmoq.a carries moq-ffi's whole dependency tree (hang, moq-audio, moq-mux, moq-native, moq-net), so an unstripped build is ~58 MB. It ships as a release tarball and Homebrew bottle rather than a git mirror, so it never hit the 100 MB git push limit that pinned moq-go, but the same thin-LTO treatment roughly halves the artifact for free. Set CARGO_PROFILE_RELEASE_LTO=thin + CODEGEN_UNITS=1 on both build paths: rs/libmoq/build.sh (the Windows cargo path) and the crane derivation in nix/overlay.nix (the Linux/macOS path). Measured: 57.8 MB -> 26.9 MB via cargo, and the nix build drops to 21.3 MB. All 50 hand-written C exports survive LTO (verified identical symbol set), and the header / cmake / pkgconfig artifacts are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- nix/overlay.nix | 8 ++++++++ rs/libmoq/build.sh | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/nix/overlay.nix b/nix/overlay.nix index d6f3514de..0f8a82faa 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -83,6 +83,14 @@ in doCheck = false; nativeBuildInputs = with final; [ pkg-config ]; + # libmoq.a carries moq-ffi's whole dep tree, so an unstripped build is + # ~75 MB+. Thin LTO with a single codegen unit dead-strips the unused + # monomorphizations Rust bakes into a staticlib, halving the artifact + # with no source or ABI change, which keeps the release tarball and + # brew download small. Mirrors rs/libmoq/build.sh's Windows cargo path. + CARGO_PROFILE_RELEASE_LTO = "thin"; + CARGO_PROFILE_RELEASE_CODEGEN_UNITS = "1"; + # libmoq is a staticlib; crane's default install phase only handles # binaries. Lay out the artifact tree the way release tarballs and # downstream `find_package(moq)` consumers already expect. diff --git a/rs/libmoq/build.sh b/rs/libmoq/build.sh index 206e3fabf..a308ea03c 100755 --- a/rs/libmoq/build.sh +++ b/rs/libmoq/build.sh @@ -16,6 +16,17 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" WORKSPACE_DIR="$(cd "$RS_DIR/.." && pwd)" +# Shrink the release staticlib. libmoq.a carries the same heavy dep tree as +# moq-ffi, so an unstripped build is ~75 MB+. libmoq ships as a release +# tarball (not a git mirror, so no hard 100 MB limit like moq-go), but thin +# LTO with a single codegen unit dead-strips the unused monomorphizations Rust +# bakes into a staticlib, halving the artifact with no source or ABI changes. +# This covers the Windows cargo path below; the nix/crane path (Linux/macOS) +# sets the same vars in nix/overlay.nix. Scoped here so a plain +# `cargo build --release` stays fast; a caller can still override. +export CARGO_PROFILE_RELEASE_LTO="${CARGO_PROFILE_RELEASE_LTO:-thin}" +export CARGO_PROFILE_RELEASE_CODEGEN_UNITS="${CARGO_PROFILE_RELEASE_CODEGEN_UNITS:-1}" + TARGET="" VERSION="" OUTPUT_DIR="dist"