Linux support: 6 patches landing 172/177 automation tests, with one cluster of 5 Linux-specific failures
Summary
URLab's README hedges "Windows (Win64). Linux is experimental", and Scripts/build_and_test.sh exists but currently hardcodes Win64 binaries. With six small patches I was able to compile URLab against UE 5.7.4 binary distribution on Ubuntu 22.04 / NVIDIA L40S and run the URLab.* automation suite to 172 / 177 passing.
Posting an issue first per CONTRIBUTING.md ("for large changes, open an issue first to discuss the approach") before sending a PR. The 5 remaining failures cluster on one root cause that I'd like guidance on before submitting.
What problem does this solve?
Researchers and CI environments running on Linux currently can't use URLab without first hitting six undocumented build- and runtime-time blockers. Several of them are real bugs (incorrect preprocessor branching, case-sensitivity, hardcoded .dll names) that are non-functional on Linux but currently no-op on Windows because of defined(_WIN32) semantics or case-insensitive filesystems.
Environment
|
|
| OS |
Ubuntu 22.04 |
| GPU |
NVIDIA L40S (Vulkan SM5) |
| UE |
5.7.4 binary (Linux_Unreal_Engine_5.7.x.zip) |
| Toolchain |
UE-bundled clang 20.1.8 + libc++ |
| CMake |
3.31 (system 3.22 is below CoACD's required 3.24) |
Patches (each minimal, all Linux-only conditional except where noted)
1. Source/URLab/URLab.Build.cs — drop _WIN32=0 define on Linux
-if (Target.Platform == UnrealTargetPlatform.Linux)
-{
- PublicDefinitions.Add("_WIN32=0");
- PublicDefinitions.Add("USE_DECLSPEC=1");
- PublicDefinitions.Add("__linux__=1");
- PublicDefinitions.Add("__unix__=1");
-}
#if defined _WIN32 is true even when _WIN32 is 0. Defining it on Linux forces MuJoCo's mjexport.h down the __declspec(dllimport) branch, which clang on Linux can't parse.
2. Source/URLab/Public/URLab.h — guard _WIN32 redefine
-#ifndef _WIN32
- #define _WIN32 PLATFORM_WINDOWS
-#endif
+#if PLATFORM_WINDOWS && !defined(_WIN32)
+ #define _WIN32 1
+#endif
Same root cause as #1 from a different angle.
3. Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h — fix doc comment
-/** @brief The ZMQ Endpoint for this specific camera (e.g., tcp://*:5558). Must be unique per camera. */
+/** @brief The ZMQ Endpoint for this specific camera (e.g., tcp://0.0.0.0:5558). Must be unique per camera. */
The ://* inside a /** … */ block triggers -Wcomment ("/* within block comment"), which is -Werror in UE's build. Cosmetic but a hard build failure.
4. Source/URLab/Public/CoACD/CoacdInterface.h — fix include casing
-#include "Coacd/coacd.h"
+#include "CoACD/coacd.h"
The installed header is at third_party/install/CoACD/include/CoACD/coacd.h. Linux fs is case-sensitive; Windows isn't.
5. Source/URLab/Private/URLab.cpp + MuJoCo/Core/MjPhysicsEngine.cpp — platform-aware shared-library names
LoadDependencyDLL("mujoco.dll", …) and GetDllHandle(TEXT("mujoco.dll")) fail on Linux. Branched the names on PLATFORM_WINDOWS / PLATFORM_LINUX and the install subdirectory (bin/ Windows, lib/ Linux, matching what the build scripts produce).
6. CoACD upstream header — _WIN32 undef guard
third_party/CoACD/src/public/coacd.h line 10:
-#if _WIN32
+#if defined(_WIN32)
This is a CoACD upstream issue triggered by UE's -Wundef -Werror. This patch lives in a third-party submodule, so it can't go in URLab proper. I'd suggest applying it via the existing third_party/CoACD_custom/ overlay pattern that URLab already uses to swap CoACD's CMakeLists. Open question for maintainers (see below).
Build-script changes
Scripts/build_and_test.sh is hardcoded to Win64 paths (UnrealBuildTool.exe, UnrealEditor-Cmd.exe, Win64 Development) — the Linux invocation in CONTRIBUTING.md would actually fail. I have a Linux variant ready (build_and_test_linux.sh) that mirrors the summary-block format. Open question whether you'd prefer a unified platform-detecting script or a parallel _linux.sh file.
third_party/build_all.sh works as-is, but only with the system gcc + libstdc++, which produces libs that fail to link into UE plugins on Linux (UE uses bundled libc++ — std symbol mismatch at plugin link time). I have a wrapper that exports CC=<UE-clang>, CXX=<UE-clang++>, CXXFLAGS="-stdlib=libc++ -nostdinc++ …" and other flags to make the third-party builds ABI-compatible with UE's link.
- Editor builds on Linux don't stage
RuntimeDependencies next to the plugin .so, so the URLab plugin's third-party shared libs aren't dlopen-able from Plugins/.../Binaries/Linux/ without LD_LIBRARY_PATH. The build_and_test script and a launcher script handle this; for shipped plugin packaging this would need proper RPATH or symlinking. Open question whether you have a preferred staging approach.
Test results (with patches 1–5; #6 applied via direct edit, not yet via overlay)
=== URLab build+test summary ===
Timestamp : 2026-04-27 00:40:49 UTC
Git HEAD : 7417660c (main)
Engine : UE 5.7.4 (Linux binary distribution)
Build : Succeeded
Tests : 172 / 177 passed (5 failed) [177 tests performed]
Log : sha256:9bec19373b91454d (4.8 MB; available on request)
================================
The 5 failures
All 5 cluster on MJCF default-class inheritance:
| Test |
Assertion |
Note |
URLab.Import.DefaultClassJointAxis |
inherited jnt_axis[1] ≈ 1 not true |
child joint isn't getting axis from <default> class |
URLab.Import.DefaultFromTo |
inherited fromto mismatch |
same family |
URLab.Import.RoundTrip_Defaults |
friction[0] expected 0.700, got 1.000 |
got MuJoCo's hardcoded fallback — class default never applied |
URLab.Muscle.Arm26_ActuatorParams |
nu mismatch: ref=6 got=3 |
URLab importer dropped half the muscles vs mj_loadXML reference |
URLab.Muscle.Arm26_Counts |
ntendon expected 6 got 3, nu expected 6 got 3 |
same — exactly half |
Two reads on this:
-
(a) genuine Linux-only bug in URLab's import path. The 6→3 exactly half Arm26 pattern strongly suggests something is iterating with stride 2 or skipping odd/even, or that default-class lookups are returning null and only the muscles with all attributes inline survive. A plausible root cause is TCHAR being UTF-16 on Windows but UTF-8 (1-byte) on Linux — a buffer-size or pointer-arithmetic assumption made on the Windows side could be skipping every other class lookup on Linux. (Speculative; haven't traced the import code.)
-
(b) you may already know about this. Wanted to surface it before going deeper.
How to reproduce locally
UE 5.7.4 binary distribution from https://www.unrealengine.com/linux, host project with the URLab plugin in Plugins/, then:
# 1. Init submodules + build third-party with UE's bundled clang + libc++
# (paths assumed: $UE_ROOT, $URLAB_ROOT)
cd $URLAB_ROOT
git submodule update --init --recursive
UE_TC=$UE_ROOT/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v26_clang-20.1.8-rockylinux8/x86_64-unknown-linux-gnu
CC=$UE_TC/bin/clang CXX=$UE_TC/bin/clang++ \
CXXFLAGS="-stdlib=libc++ -nostdinc++ -isystem $UE_TC/include/c++/v1 -fPIC -Qunused-arguments -Wno-unknown-warning-option -Wno-missing-template-arg-list-after-template-kw" \
CFLAGS="-fPIC -Qunused-arguments -Wno-unknown-warning-option" \
LDFLAGS="-stdlib=libc++ -fuse-ld=lld -L$UE_TC/lib64 -Wl,-rpath,$UE_TC/lib64" \
bash third_party/build_all.sh --no-submodule-sync
# 2. drop libzmq.a so UBT links against libzmq.so (avoids
# pthread_cond_clockwait undefined-symbol from static archive)
rm third_party/install/libzmq/lib/libzmq.a
# 3. Generate project files + build editor target
$UE_ROOT/Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh -project=<host>.uproject -game -engine
$UE_ROOT/Engine/Build/BatchFiles/Linux/Build.sh <Project>Editor Linux Development -Project=<host>.uproject
# 4. Launch editor with LD_LIBRARY_PATH to the third-party install dirs
URLAB_3P=$URLAB_ROOT/third_party/install
LD_LIBRARY_PATH="$URLAB_3P/MuJoCo/lib:$URLAB_3P/CoACD/lib:$URLAB_3P/libzmq/lib" \
DISPLAY=:1 $UE_ROOT/Engine/Binaries/Linux/UnrealEditor <host>.uproject
Questions before I send a PR
- Are you interested in a Linux-support PR at all? Status of the
0.1-alpha "experimental" tag suggests yes, but you may have your own Linux roadmap.
- CoACD
_WIN32 patch — apply via CoACD_custom/ overlay (replicates your existing pattern), or upstream PR to CoACD? (Overlay is faster but adds a maintenance touchpoint.)
- Build script — unified platform-detecting
build_and_test.sh, or a parallel _linux.sh?
- Third-party build with UE toolchain — is the right place to express this a new
third_party/build_all_linux.sh, or extend build_all.sh with platform detection?
- Default-class inheritance bug — would you prefer me to investigate and fix as part of the same PR, or carve it out into a separate tracking issue and ship the build/runtime support first?
- Plugin runtime staging on Linux — is
LD_LIBRARY_PATH acceptable for editor dev workflows (with packaging adding proper RPATH), or do you want a different convention?
Happy to do the work either way — just want to align on shape before I open a multi-file PR you might prefer differently scoped. The patches are small and tested locally; I can have a PR up within a day of your guidance.
Importance
Research / exploration. Not blocking my project (I have a Linux build working locally), but Linux-only research labs would currently be unable to evaluate URLab without rediscovering all six issues themselves.
Linux support: 6 patches landing 172/177 automation tests, with one cluster of 5 Linux-specific failures
Summary
URLab's README hedges "Windows (Win64). Linux is experimental", and
Scripts/build_and_test.shexists but currently hardcodes Win64 binaries. With six small patches I was able to compile URLab against UE 5.7.4 binary distribution on Ubuntu 22.04 / NVIDIA L40S and run theURLab.*automation suite to 172 / 177 passing.Posting an issue first per
CONTRIBUTING.md("for large changes, open an issue first to discuss the approach") before sending a PR. The 5 remaining failures cluster on one root cause that I'd like guidance on before submitting.What problem does this solve?
Researchers and CI environments running on Linux currently can't use URLab without first hitting six undocumented build- and runtime-time blockers. Several of them are real bugs (incorrect preprocessor branching, case-sensitivity, hardcoded
.dllnames) that are non-functional on Linux but currently no-op on Windows because ofdefined(_WIN32)semantics or case-insensitive filesystems.Environment
Linux_Unreal_Engine_5.7.x.zip)Patches (each minimal, all Linux-only conditional except where noted)
1.
Source/URLab/URLab.Build.cs— drop_WIN32=0define on Linux#if defined _WIN32is true even when_WIN32is0. Defining it on Linux forces MuJoCo'smjexport.hdown the__declspec(dllimport)branch, which clang on Linux can't parse.2.
Source/URLab/Public/URLab.h— guard_WIN32redefineSame root cause as #1 from a different angle.
3.
Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h— fix doc commentThe
://*inside a/** … */block triggers-Wcomment("/*within block comment"), which is-Werrorin UE's build. Cosmetic but a hard build failure.4.
Source/URLab/Public/CoACD/CoacdInterface.h— fix include casingThe installed header is at
third_party/install/CoACD/include/CoACD/coacd.h. Linux fs is case-sensitive; Windows isn't.5.
Source/URLab/Private/URLab.cpp+MuJoCo/Core/MjPhysicsEngine.cpp— platform-aware shared-library namesLoadDependencyDLL("mujoco.dll", …)andGetDllHandle(TEXT("mujoco.dll"))fail on Linux. Branched the names onPLATFORM_WINDOWS/PLATFORM_LINUXand the install subdirectory (bin/Windows,lib/Linux, matching what the build scripts produce).6. CoACD upstream header —
_WIN32undef guardthird_party/CoACD/src/public/coacd.hline 10:This is a CoACD upstream issue triggered by UE's
-Wundef -Werror. This patch lives in a third-party submodule, so it can't go in URLab proper. I'd suggest applying it via the existingthird_party/CoACD_custom/overlay pattern that URLab already uses to swap CoACD's CMakeLists. Open question for maintainers (see below).Build-script changes
Scripts/build_and_test.shis hardcoded to Win64 paths (UnrealBuildTool.exe,UnrealEditor-Cmd.exe,Win64 Development) — the Linux invocation inCONTRIBUTING.mdwould actually fail. I have a Linux variant ready (build_and_test_linux.sh) that mirrors the summary-block format. Open question whether you'd prefer a unified platform-detecting script or a parallel_linux.shfile.third_party/build_all.shworks as-is, but only with the system gcc + libstdc++, which produces libs that fail to link into UE plugins on Linux (UE uses bundled libc++ — std symbol mismatch at plugin link time). I have a wrapper that exportsCC=<UE-clang>,CXX=<UE-clang++>,CXXFLAGS="-stdlib=libc++ -nostdinc++ …"and other flags to make the third-party builds ABI-compatible with UE's link.RuntimeDependenciesnext to the plugin.so, so the URLab plugin's third-party shared libs aren't dlopen-able fromPlugins/.../Binaries/Linux/withoutLD_LIBRARY_PATH. The build_and_test script and a launcher script handle this; for shipped plugin packaging this would need properRPATHor symlinking. Open question whether you have a preferred staging approach.Test results (with patches 1–5; #6 applied via direct edit, not yet via overlay)
The 5 failures
All 5 cluster on MJCF default-class inheritance:
URLab.Import.DefaultClassJointAxisinherited jnt_axis[1] ≈ 1not trueaxisfrom<default>classURLab.Import.DefaultFromToinherited fromtomismatchURLab.Import.RoundTrip_Defaultsfriction[0]expected0.700, got1.000URLab.Muscle.Arm26_ActuatorParamsnu mismatch: ref=6 got=3mj_loadXMLreferenceURLab.Muscle.Arm26_Countsntendonexpected 6 got 3,nuexpected 6 got 3Two reads on this:
(a) genuine Linux-only bug in URLab's import path. The
6→3 exactly halfArm26 pattern strongly suggests something is iterating with stride 2 or skipping odd/even, or that default-class lookups are returning null and only the muscles with all attributes inline survive. A plausible root cause isTCHARbeing UTF-16 on Windows but UTF-8 (1-byte) on Linux — a buffer-size or pointer-arithmetic assumption made on the Windows side could be skipping every other class lookup on Linux. (Speculative; haven't traced the import code.)(b) you may already know about this. Wanted to surface it before going deeper.
How to reproduce locally
UE 5.7.4 binary distribution from https://www.unrealengine.com/linux, host project with the URLab plugin in
Plugins/, then:Questions before I send a PR
0.1-alpha"experimental" tag suggests yes, but you may have your own Linux roadmap._WIN32patch — apply viaCoACD_custom/overlay (replicates your existing pattern), or upstream PR to CoACD? (Overlay is faster but adds a maintenance touchpoint.)build_and_test.sh, or a parallel_linux.sh?third_party/build_all_linux.sh, or extendbuild_all.shwith platform detection?LD_LIBRARY_PATHacceptable for editor dev workflows (with packaging adding properRPATH), or do you want a different convention?Happy to do the work either way — just want to align on shape before I open a multi-file PR you might prefer differently scoped. The patches are small and tested locally; I can have a PR up within a day of your guidance.
Importance
Research / exploration. Not blocking my project (I have a Linux build working locally), but Linux-only research labs would currently be unable to evaluate URLab without rediscovering all six issues themselves.