Fix Windows natives: separate audio/GLFW DLLs + macos-x86_64 cross-link - #11
Closed
MateuszKubuszok wants to merge 54 commits into
Closed
Fix Windows natives: separate audio/GLFW DLLs + macos-x86_64 cross-link#11MateuszKubuszok wants to merge 54 commits into
MateuszKubuszok wants to merge 54 commits into
Conversation
Builds SGE Rust native libraries and packages them as fat JARs with native/<platform>/ layout for consumption via sbt-multi-arch-release NativeLibBundlePlugin. Five provider artifacts: - scala-native-sge-ops-provider: sge_native_ops.a, sge_audio.a, glfw3.a, freetype.a for Scala Native static linking (6 desktop platforms) - panama-sge-ops-provider: .dylib/.so/.dll for JVM Panama FFM runtime loading (6 desktop platforms) - android-sge-ops-provider: .so for Android JNI (3 ABIs) - scala-native-angle-provider: EGL + GLESv2 for Scala Native - panama-angle-provider: EGL + GLESv2 for JVM Panama Rust source from sge/sge-deps/native-components/ with vendored GLFW and miniaudio (git submodules). Cross-compilation via cargo-zigbuild (Linux), cargo-xwin (Windows), native cargo (macOS), NDK (Android). CI validates JAR contents — fails fast if expected native files are missing for any platform.
Rust workspace decomposition: - Monolithic sge_native_ops crate → 3 independent crates: core/ → libsge_native_ops (ETC1, buffer_ops, audio, gdx2d) freetype/ → libsge_freetype (FreeType font rasterization) physics/ → libsge_physics (Rapier2D 2D physics) - Removed dead JNI bridge (Android uses PanamaPort, not JNI) Test coverage (0 → 88 tests): - buffer_ops: 36 tests (copy, transforms, vertex find/compare) - etc1: 22 tests (codec, PKM header, encode/decode roundtrip) - gdx2d: 5 tests (PNG decode, invalid data, null safety) - freetype: 5 tests (library lifecycle, stroker, null safety) - physics: 10 tests (world lifecycle, gravity sim, raycast, body ops) Provider JARs restructured (5 → 11): - Renamed *-sge-ops-* → *-sge-core-* (no more freetype in core) - Added: scala-native/panama/android providers for freetype + physics - Windows companion .lib stubs embedded in core provider - Linux libobjc.a stub embedded in core provider - Updated cross-all.sh and cross-android.sh for workspace builds - CI validates all 11 provider JARs
…ertia, AABB queries (ISS-457) - Collision filtering: set/get collision_groups and solver_groups for colliders - Revolute joint: enable_limits, set/get_limits, is_limit_enabled, enable_motor, set/get_motor_speed, set_max_motor_torque, get_angle - Prismatic joint: enable_limits, set/get_limits, enable_motor, set/get_motor_speed, set_max_motor_force, get_translation - Body mass properties: get_mass, get_inertia, get_local_center_of_mass, recompute_mass_properties - AABB queries: query_aabb returns collider handles within bounding box
- Rename all 11 provider directories and artifacts to follow sn-provider-*/pnm-provider-* naming convention - Consolidate ops providers: sn-provider-sge, pnm-provider-sge-desktop, pnm-provider-sge-android (was scala-native-sge-ops, panama-sge-ops, android-sge-ops) - sn-provider-sge and pnm-provider-sge-desktop depend transitively on their respective angle providers - Replace native-bundle.json with sn-provider.json (new format with binary field, flags-groups, provider-schema-version) - Add pnm-provider.json manifests for all Panama providers (desktop and android) — previously had no manifests - Use multiarch-core Platform model in build.sbt instead of hand-written platform strings - Add SNAPSHOT publishing from PR builds - Update CI JAR verification and README
…imesh, heightfield, joint getters, spring joint, shape cast, ray cast all, point projection, solver params
…, collider getters, trimesh, heightfield, spring joint, shape cast, solver params)
…collect physics3d artifacts
- Add sn-provider-sge-physics3d, pnm-provider-sge-physics3d-desktop, pnm-provider-sge-physics3d-android provider projects to build.sbt - Add sge_phys_intersect_shape FFI function for shape overlap queries
Add 92 new C ABI functions covering: - Body: torque impulse, reset forces/torques, type query, per-axis translation/rotation locking, world/local COM, inertia, velocity at point - Collider: all property getters, enable/disable, position (world + relative), shape type, AABB, parent body, mass, contact skin, active events, collision types, collision/solver groups - Shapes: heightfield (3D grid) - Joints: revolute (3D axis+anchor), prismatic (3D axis), motor (6-DOF), spring — all with limits, motors, and getters - Queries: AABB, point, ray cast all, project point, cast shape, intersect shape - Contact detail queries (7 floats per 3D contact point) - Intersection events, solver parameters
…D + 3D) - Buffer ContactForceEvent during step, add poll_contact_force_events FFI - Add collider contact_force_event_threshold get/set - Implement SgePhysicsHooks with one-way platform support via Rapier's update_as_oneway_platform helper - Add collider active_hooks flags and set/get_one_way_direction FFI - All existing tests pass (16 in 2D, 5 in 3D)
The @link("objc") annotation on ObjCRuntime in SGE core causes the Scala Native linker to require objc.lib on Windows. Linux already had a libobjc.a stub; Windows was missing the equivalent. On macOS the system libobjc is used directly. Generate objc.lib stubs for windows-x86_64 and windows-aarch64 using zig cc (same approach as the Linux stubs), include them in the sn-provider-sge JAR, and verify their presence in CI.
Three bugs in Windows ANGLE packaging: 1. build.sbt: allow set had "GLESv2.dll" but archive contains "libGLESv2.dll" (lib prefix) — DLL was silently excluded from JAR 2. download-angle.sh: never copied .dll.lib import libraries from the ANGLE archive. Windows MSVC linker needs these to resolve GL symbols. Now copies libEGL.dll.lib → EGL.lib, libGLESv2.dll.lib → GLESv2.lib 3. EGL.lib and GLESv2.lib were in sn-provider-sge core JAR as Rust companion stubs (no GL symbols), masking the missing ANGLE imports. Moved to sn-provider-sge-angle where they belong. Without this fix, Scala Native linking on Windows fails with "unresolved external symbol glCullFace" etc. because the linker finds the empty Rust companion stub instead of the ANGLE import lib.
GLFW on Windows needs gdi32, shell32, user32 (DragAcceptFiles, CreateDIBSection, ChoosePixelFormat etc. from Win32 API). miniaudio needs ole32 and winmm. These are auto-linked by MSVC but Scala Native uses clang++ which requires explicit -l flags. Without this, Scala Native linking on Windows fails with 17 unresolved externals from GLFW's win32_window.o and win32_monitor.o.
Documents licenses for GLFW (zlib), miniaudio (Unlicense/MIT-0), ANGLE (BSD-3), and Rust crate dependencies linked at build time.
Configure [workspace.lints.clippy] to establish a sensible lint policy for this FFI-heavy C ABI codebase: - Allow missing_safety_doc, too_many_arguments, unnecessary_cast (inherent to C ABI libraries with no Rust callers) - Allow pedantic cast lints that are pure noise for FFI code (cast_sign_loss, cast_possible_truncation, ptr_cast_constness, etc.) - Enable useful pedantic lints as warnings (manual_range_contains, needless_pass_by_value, redundant_closure) All 4 member crates opt in via [lints] workspace = true.
- build.rs: remove needless borrows on format!() and array literals - etc1.rs: use RangeInclusive::contains and iterator enumerate - physics3d: remove useless .into() conversions (Vec3 -> Vec3), simplify convex_hull call, replace index loops with slice::fill
Add rustfmt.toml (edition 2021) and apply cargo fmt --all to normalize all source files to standard Rust formatting.
Add a new 'lint' job that runs in parallel with build-and-package. Runs cargo clippy --workspace -- -D warnings and cargo fmt --check to catch regressions before the full cross-compilation build.
- Bump sbt-kubuszok from 0.1.0 to 0.2.0 - Add mimaSettings (mimaPreviousArtifacts, mimaFailOnNoPrevious, packageDoc/publishArtifact := false) to all modules - Move packageDoc setting from ThisBuild into mimaSettings - Sort provider modules alphabetically within each category - Sort root project aggregation alphabetically by category - Use .settings(publishSettings) without splat (consistent with sge) - Add noPublishSettings + mimaSettings to root project
- project/plugins.sbt: multiarch-core 0.1.2 -> 0.3.0, sbt-kubuszok 0.2.0 -> 0.2.3 - project/build.properties: sbt.version 1.12.11 -> 2.0.0 - build.sbt: packageBin mappings are now keyed by xsbti.HashedVirtualFileRef in sbt 2.0; convert each native File via fileConverter.toVirtualFile - build.sbt: drop sbt-welcome usage (logo/usefulTasks); the plugin is no longer bundled by sbt-kubuszok 0.2.3 on sbt 2.0 Resource-only build; packageBin verified to still emit native/<classifier>/<file> mappings intact.
sbt 2.0 writes packageBin output to target/out/jvm/u/<name>/<name>-<ver>.jar at the build root, not providers/<name>/target/*.jar as in sbt 1.x. The verify step's hardcoded sbt-1.x globs found nothing and failed. Replace each 'ls providers/<name>/target/*.jar' with a layout-robust 'find target/out/jvm/u/<name>' lookup that excludes sources/javadoc jars. Native build paths (native-components/target/cross/...) are unchanged.
Two native-provider packaging defects surfaced by sge's sgeValidateNativeLibs gate (ISS-484) and Windows JVM tests: 1. ANGLE Windows DLL naming. SGE/multiarch load ANGLE by the logical names "EGL"/"GLESv2", which NativeLibLoader maps to EGL.dll/GLESv2.dll on Windows (no "lib" prefix, unlike System.mapLibraryName on macOS/Linux). The upstream archives ship libEGL.dll/libGLESv2.dll, and the pnm-provider packaged/promised those prefixed names — so the JVM could never resolve them at runtime and the gate reported GLESv2.dll absent. download-angle.sh now stages both names side by side (libEGL.dll/libGLESv2.dll for the sn-provider, EGL.dll/GLESv2.dll for the pnm-provider); the pnm-angle manifest + packaging now reference the unprefixed Windows names. 2. macos-x86_64 audio/glfw 16 KB stubs. build_audio_bridge_shared and build_glfw_shared invoked the cc compiler for the final dylib link without carrying cc_tool.args() — dropping the -arch x86_64 flag on cross builds. Linking x86_64 objects with the host (arm64) default produced non-functional ~16 KB stub dylibs. The link step now forwards the compiler's own args so it targets the same arch as the archive. 3. Windows glfw export. Defined _GLFW_BUILD_DLL on the Windows shared-DLL path so GLFWAPI resolves to __declspec(dllexport); MSVC/lld-link export nothing by default, so without it glfw3.dll exposes no glfw* symbols. Note: the Windows audio bridge (sge_audio_*) still needs an export mechanism, and the merge-into-sge_native_ops.dll strategy on Windows should be revisited — see report. These are not verifiable without a Windows cross-compile and are left for CI.
The published pnm-provider-sge-desktop JAR was missing the Windows sge_audio.dll / glfw3.dll, and merging the C libs into sge_native_ops.dll destabilised that cdylib's load (UnsatisfiedLinkError: sge_native_ops on the Windows JVM). A Rust cdylib only re-exports #[no_mangle] Rust symbols, so audio/GLFW objects linked into sge_native_ops.dll were swallowed and exported nowhere. Fix by building separate DLLs on Windows-cross like every other platform, and exporting the C symbols those DLLs need: - build.rs: drop the merge_into_cdylib path (Windows-cross now uses the existing build_audio_bridge_shared / build_glfw_shared lld-link path). Keep sge_native_ops a clean Rust-only cdylib. The merged helpers are retained but #[allow(dead_code)]. - build.rs: name the Windows GLFW DLL glfw3.dll (was glfw.dll) so the artifact-collection step and JVM loader actually pick it up. This mismatch was latent while the merge path bypassed build_glfw_shared. - sge_audio_bridge.c: add SGE_AUDIO_API (__declspec(dllexport) on Windows, no-op elsewhere) and apply it to all 41 public sge_audio_* functions so sge_audio.dll exports them — mirrors GLFW's _GLFW_BUILD_DLL fix. - ci.yml: assert the Windows DLLs (sge_native_ops.dll, sge_audio.dll, glfw3.dll) are present in the desktop provider JAR — the gap that let the missing-DLL regression ship undetected. Host build re-confirmed: real libsge_audio.dylib (41 exports) + libglfw.dylib produced; clippy + rustfmt clean.
CI's first run linked windows-x86_64 fine but failed windows-aarch64 with lld-link: undefined symbol: _initterm (EC symbol) (and the rest of the CRT startup/onexit family, all "EC symbol"). /machine:arm64x produces a hybrid ARM64X image that additionally requires the ARM64EC-flavoured CRT, but the xwin crt/ucrt/vcruntime import libs we pass are the pure-ARM64 set, so the EC symbols were left undefined. We want a plain ARM64 DLL: switch both lld-link sites (sge_audio.dll and glfw3.dll) to /machine:arm64. The libpaths already point at the arm64 import libs, so this is now consistent. This path was never exercised before because Windows-cross used to merge the C libs into sge_native_ops.dll instead of building separate DLLs.
After switching windows-aarch64 to /machine:arm64, the link still crashed: lld::coff::ImportFile::parse() SIGSEGV while parsing the ARM64 Windows SDK import libs. The crash backtrace showed rust-lld (rustup's bundled lld) was used — the lld_link_command() helper only probed /opt/homebrew/opt/lld/bin/lld, which doesn't exist on the CI runner, so it fell back to `lld-link` on PATH, which resolved to the older, crashing rust-lld. Probe the real LLVM lld-link from Homebrew's `llvm`/`lld` formulae explicitly (/opt/homebrew/opt/llvm/bin/lld-link first — that's what CI's `brew install llvm` provides and adds to PATH), covering both Apple-silicon and Intel prefixes, and only fall back to PATH `lld-link` last. The newer lld parses the arm64 import libs without crashing.
Iteration 3 still crashed in rust-lld: the explicit /opt/homebrew/opt/llvm/bin probe didn't match on the runner and build.rs fell back to PATH `lld-link`, which resolves to rustup's rust-lld (it crashes on the ARM64 import libs). Make it deterministic: - CI/release: resolve the LLVM prefix with `brew --prefix llvm`, ls the lld-link to fail loudly if absent, and export SGE_LLD_LINK to its absolute path. - build.rs: honor SGE_LLD_LINK first; also scan versioned llvm@N kegs under both Homebrew Cellars as extra fallbacks before the last-resort PATH lookup.
Iteration 4 failed at the install step: ls /opt/homebrew/opt/llvm/bin/lld-link "No such file or directory". Homebrew's `llvm` formula does not include the lld driver/symlinks; lld-link comes from the separate `lld` formula. Install both `llvm` and `lld`, add each prefix to PATH, and point SGE_LLD_LINK at `$(brew --prefix lld)/bin/lld-link`. build.rs already lists that path among its candidates and honors SGE_LLD_LINK first.
With the real Homebrew lld-link, windows-x86_64 links cleanly, but windows-aarch64 now fails: "kernel32.lib(KERNEL32.dll): machine type arm64ec conflicts with arm64". cargo-xwin's arm64 Windows SDK import libs are ARM64EC-flavoured, so forcing /machine:arm64 conflicts. Build a hybrid ARM64X image instead (both lld-link sites): ARM64X accepts our arm64 object archive plus the arm64ec imports and is loadable by native ARM64 Windows processes. The earlier arm64x EC-symbol failure was rust-lld-specific; the real lld resolves the EC CRT from the same libs. Also add a CI diagnostic dumping the xwin arm64 lib layout / kernel32.lib machine type for ground truth if this needs further tuning.
ARM64X with bare lld-link still left the DLL CRT startup undefined (_initterm / _onexit / terminate "EC symbol"s): those objects are pulled by the CRT-entry logic that clang-cl's /LD adds, which a raw lld-link invocation does not. Replace both Windows-cross link blocks (sge_audio.dll, glfw3.dll) with a clang-cl /LD link driven by the xwin-wrapped compiler `cc` returns. clang-cl selects the correct target machine from its baked-in --target=<triple> and links the DLL CRT startup — including the ARM64EC CRT — so we no longer hand-pick /machine or the SDK libpaths. Whole-archive + extra system import libs are passed after -link. lld_link_command() is retained (#[allow(dead_code)]) for reference.
…build
The clang-cl /LD approach failed even for windows-x86_64 ("no input files",
and it would not inherit cargo-xwin's winsysroot anyway — xwin wraps the
linker, not the cc-crate compiler args). Restore the lld-link path that DID
link windows-x86_64 successfully and reached the arm64 EC-CRT stage.
The earlier diagnostic ran BEFORE the first cargo-xwin build, so the xwin SDK
cache was still empty (blank ls). Move it after the build with if: always()
and dump the real dir tree + per-arch kernel32.lib machine type, so the next
iteration has ground truth on whether a pure-arm64 (non-EC) SDK lib variant
exists for the windows-aarch64 link.
The post-build diagnostic revealed the cargo-xwin SDK cache layout:
- per-arch lib dirs are named aarch64 / x86_64 (NOT arm64) — our libpaths
pointed at a nonexistent .../arm64 dir, so they were silent no-ops and lld
only found libs via xwin's injected LIB env (the arm64ec set).
- the aarch64 SDK import libs are ARM64EC-flavoured.
Factor both Windows-cross links into windows_cross_lld_link() and fix all three:
- arch dir -> aarch64 / x86_64 (matches the cache).
- /machine:arm64ec for aarch64 (matches the arm64ec import libs; /machine:arm64
conflicted, /machine:arm64x left the EC CRT unresolved). arm64ec is
x64-ABI-compatible and loadable in ARM64 Windows processes.
- add msvcrt.lib so the DLL CRT startup (_initterm/_onexit/terminate/...)
resolves — those were the undefined "EC symbol"s.
windows-x86_64 keeps /machine:x64 + x86_64 dirs (unchanged, already linked OK).
Hand-picking lld-link CRT libs hit a dead end: x64 linked with kernel32/ucrt/vcruntime, but the aarch64 (arm64ec) image then left the DLL CRT startup _initterm/_onexit undefined, and adding msvcrt.lib pulled in unresolved dllimport CRT data (__argc/_commode/_daylight/...), breaking x64 too. Drive the DLL link through clang-cl /LD (the xwin-wrapped compiler), which selects the target machine and links the full DLL CRT itself. The static archive is passed as a positional input (so clang-cl has an input file — the prior "no input files" failure) and re-listed under /wholearchive: so every sge_audio_*/glfw* export survives. cc carries clang-cl's --target/winsysroot. Emit the resolved link command as a cargo:warning for diagnosability.
The diagnostic showed the clang-cl invocation was correct (target, imsvc includes, /LD, /Fe, -link, /wholearchive) yet still "no input files": clang-cl's driver only recognises .lib/.obj as linker inputs and silently ignores a Unix .a archive. Copy the built lib<name>.a to <name>.lib and pass that as both the positional input and the /wholearchive target.
The .lib alone still produced "no input files": clang-cl /LD is a compile-and-link mode and needs a translation unit, not just libraries. Write a trivial <stem>_dllmain.c (one anchor symbol) as the compile input, keep the archive copied to .lib and whole-archived. clang-cl compiles the dummy and links our exports + the full DLL CRT it pulls in for the target — fixing the arm64ec EC-CRT problem without hand-picking CRT libs.
clang-cl parses any arg starting with '/' as an MSVC option, so the absolute /Users/.../dllmain.c was read as the /U option and the driver reported 'no input files'. Run clang-cl with the dummy source's directory as cwd and pass just the bare filename; the .c extension makes it a compile input. /LD, /Fe:, /link and /wholearchive: stay absolute.
…externals) The Windows desktop provider built glfw3.dll but shipped no real import library for it: scripts/cross-all.sh fabricated glfw3.lib by COPYING sge_native_ops.lib (a stub with the wrong exports). So Scala Native's @link("glfw3") link on Windows failed with LNK2019 unresolved external symbol glfwInit (etc.) -> LNK1120 48 unresolved externals (sge Native FFI IT windows-x86_64). - build.rs (windows_cross_lld_link): pass /implib:<release_dir>/glfw3.dll.lib to the clang-cl /LD link so lld-link emits a genuine import library with the real GLFW exports, next to glfw3.dll (mirrors cargo's sge_native_ops.dll.lib). - cross-all.sh: collect glfw3.dll.lib and rename to glfw3.lib (the name @link("glfw3") resolves on Windows); drop glfw3/glfw from the bogus companion-stub list so the stub can no longer shadow the real lib. build.sbt sn-provider-sge mapping already globs glfw3.lib and CI verify_jar already asserts native/<win>/glfw3.lib, so packaging is unchanged.
…IT link) The glfw3.lib fix dropped the Windows Native FFI IT link errors 48 -> 27, revealing two more import-lib gaps of the same kind: 1. sge_audio: AudioOpsNative's @link("sge_audio") had 25 LNK2019 unresolved (sge_audio_play_sound, ...). build_audio_bridge_shared links sge_audio.dll via windows_cross_lld_link, which already emits sge_audio.dll.lib now (the /implib: added for glfw applies to it too) — but cross-all.sh still fabricated sge_audio.lib as a sge_native_ops.lib stub. Collect sge_audio.dll.lib -> sge_audio.lib and drop sge_audio from the bogus-stub list (mirrors the glfw3 fix). Already in the sn-provider-sge JAR mapping + verify_jar. 2. glfw_platform_stubs.c: glfwGetCocoaWindow / glfwGetX11Window are referenced by WindowingOpsNative.getNativeWindowHandle on every platform (Scala Native needs every @extern resolvable), but on Windows the stubs were compiled into glfw3.dll WITHOUT being exported, so glfw3.lib lacked them -> 2 LNK2019. Export the stubs from the DLL via __declspec(dllexport) under _GLFW_BUILD_DLL (mirrors GLFW's own GLFWAPI). Empty on ELF/Mach-O, which already export globals by default.
…der-sge With the real import libs in place the Windows Native FFI IT now LINKS, but the binary exits 0xC0000135 STATUS_DLL_NOT_FOUND at startup: on Windows sge_native_ops/sge_audio/glfw3 are linked via import libraries that reference the matching DLLs, so those DLLs must be present at run time. The multiarch NativeProviderPlugin copies every .dll it extracts next to the linked executable (no rpath on Windows) — but the sn-provider-sge mapping shipped only the .lib/.a link artifacts, so the runtime DLLs were never extracted/copied (only the ANGLE provider's EGL.dll/GLESv2.dll were). Add sge_native_ops.dll / sge_audio.dll / glfw3.dll to the sn-provider-sge mapping (they already exist in the windows-* cross dirs via cross-all.sh; no-op for linux/macos). Now the plugin copies them next to the exe and the FFI validation binary starts.
MateuszKubuszok
force-pushed
the
fix/windows-natives-and-macos-x86
branch
from
June 29, 2026 17:05
f3f34d6 to
9ac87f4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The published
pnm-provider-sge-desktopJAR was missing the Windowssge_audio.dll/glfw3.dll. The old build merged the C libs intosge_native_ops.dllon Windows-cross, but a Rust cdylib only re-exports#[no_mangle]Rust symbols — the C audio/GLFW symbols were swallowed andexported nowhere, and the extra C code destabilised the cdylib's load
(
UnsatisfiedLinkError: sge_native_opson the Windows JVM).Changes
This branch also carries the prior macos-x86_64
-archcross-link fix,the ANGLE Windows naming fix, and the GLFW
_GLFW_BUILD_DLLexport(commit 1fc9b2d). This commit completes the Windows audio/GLFW fix:
build.rs: drop themerge_into_cdylibpath. Windows-cross nowbuilds separate
sge_audio.dll/glfw3.dllvia the existingbuild_*_sharedlld-link path, like every other platform.sge_native_ops.dllstays a clean Rust-only cdylib. Merged helpers areretained but
#[allow(dead_code)].build.rs: name the Windows GLFW DLLglfw3.dll(wasglfw.dll)so the artifact-collection step and JVM loader pick it up — a latent
mismatch that the merge path had been bypassing.
sge_audio_bridge.c: addSGE_AUDIO_API(
__declspec(dllexport)on Windows, no-op elsewhere) on all 41 publicsge_audio_*functions sosge_audio.dllactually exports them.Mirrors GLFW's
_GLFW_BUILD_DLL.ci.yml: assert the Windows DLLs are present in the desktopprovider JAR — the verification gap that let the regression ship.
Verification
cargo build --releaseproduces reallibsge_audio.dylib(41 exportedsge_audio_*symbols) +libglfw.dylib;sge_native_opsstays clean. clippy + rustfmt clean.