diff --git a/README.md b/README.md index aa1fc7d..1ab16d6 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,9 @@ URLab communicates with external systems over ZMQ. The companion package [**urla ## Requirements -- **Unreal Engine 5.7+** -- **Windows** (Win64). Linux is experimental. +- **Unreal Engine 5.7+** -- Windows binary or Linux binary distribution. +- **Windows (Win64)** with **Visual Studio 2022**, or **Linux (x86_64)** with UE's bundled clang. See [Linux setup](docs/linux_setup.md) for the Linux-specific build flow. - **MuJoCo 3.7+** -- bundled in `third_party/`, built from source. -- **Visual Studio 2022** or compatible C++ toolchain. - **CMake 3.24+** -- for building third-party libraries. - **Python 3.11+** -- optional, for `urlab_bridge` policies. - **[uv](https://github.com/astral-sh/uv)** -- optional, for Python dependency management. @@ -71,10 +70,17 @@ git clone https://github.com/URLab-Sim/UnrealRoboticsLab.git ### 2. Build Dependencies Navigate to the plugin's `third_party` folder and run the build script to fetch and compile MuJoCo, CoACD, and ZMQ: ```powershell +# Windows (PowerShell) cd UnrealRoboticsLab/third_party .\build_all.ps1 ``` -*(If this script fails with a **Stack Overflow** error, see [Troubleshooting](#troubleshooting) below).* +```bash +# Linux: see docs/linux_setup.md for the env vars needed to build +# the third-party libs against UE's bundled clang + libc++. +cd UnrealRoboticsLab/third_party +./build_all.sh +``` +*(If the Windows script fails with a **Stack Overflow** error, see [Troubleshooting](#troubleshooting) below).* ### 3. Compile & Launch 1. Right-click your `.uproject` and select **Generate Visual Studio project files**. diff --git a/Scripts/build_and_test_linux.sh b/Scripts/build_and_test_linux.sh new file mode 100755 index 0000000..496ff02 --- /dev/null +++ b/Scripts/build_and_test_linux.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Jonathan Embley-Riches. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# --- LEGAL DISCLAIMER --- +# UnrealRoboticsLab is an independent software plugin. It is NOT affiliated with, +# endorsed by, or sponsored by Epic Games, Inc. "Unreal" and "Unreal Engine" are +# trademarks or registered trademarks of Epic Games, Inc. in the US and elsewhere. +# +# This plugin incorporates third-party software: MuJoCo (Apache 2.0), +# CoACD (MIT), and libzmq (MPL 2.0). See ThirdPartyNotices.txt for details. + +# Linux variant of build_and_test.sh. Same summary block format so the PR +# template is satisfied. Differs from the upstream script in: +# - Uses the Linux Build.sh / UnrealEditor-Cmd binaries +# - Builds the project's Editor target on the Linux platform +# - Stages third-party shared libs into the plugin's Binaries/Linux/ via +# setup_runtime_linux.sh after build (RuntimeDependencies aren't auto- +# staged for editor builds on Linux). +# +# Usage: +# ./Scripts/build_and_test_linux.sh \ +# --engine /home/ubuntu/UnrealEngine \ +# --project /home/ubuntu/URLabTest/URLabTest.uproject \ +# [--target CustomEditorTargetName] \ +# [--filter URLab] \ +# [--log /tmp/urlab_test.log] +# +# --target defaults to Editor derived from the .uproject filename. +# Only override for projects that don't follow UE's naming convention. +# +# Exit codes: 0 ok, 1 build failed, 2 tests failed, 3 bad args. + +set -eu + +TARGET="" +FILTER="URLab" +LOG="/tmp/urlab_test.log" +ENGINE="" +PROJECT="" + +usage() { + cat >&2 <<'HELP' +build_and_test_linux.sh — build the project's Editor target and run the URLab automation suite (Linux). + +Usage: + ./Scripts/build_and_test_linux.sh \ + --engine /home/ubuntu/UnrealEngine \ + --project /home/ubuntu/URLabTest/URLabTest.uproject \ + [--target CustomEditorTargetName] \ + [--filter URLab] \ + [--log /tmp/urlab_test.log] + +--target defaults to Editor derived from the .uproject filename. + +Exit codes: 0 ok, 1 build failed, 2 tests failed, 3 bad args. +HELP + exit 3 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --engine) ENGINE="$2"; shift 2 ;; + --project) PROJECT="$2"; shift 2 ;; + --target) TARGET="$2"; shift 2 ;; + --filter) FILTER="$2"; shift 2 ;; + --log) LOG="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "Unknown arg: $1" >&2; usage ;; + esac +done + +[[ -z "$ENGINE" ]] && { echo "Missing --engine" >&2; usage; } +[[ -z "$PROJECT" ]] && { echo "Missing --project" >&2; usage; } + +# Derive the editor target from the .uproject filename if not explicitly +# supplied. UE's convention is Editor — e.g. MyGame.uproject -> +# MyGameEditor. Override with --target for projects that don't follow this. +if [[ -z "$TARGET" ]]; then + PROJECT_BASENAME=$(basename "$PROJECT") + TARGET="${PROJECT_BASENAME%.*}Editor" +fi + +BUILD_SH="$ENGINE/Engine/Build/BatchFiles/Linux/Build.sh" +CMD="$ENGINE/Engine/Binaries/Linux/UnrealEditor-Cmd" + +[[ -x "$BUILD_SH" ]] || { echo "Build.sh not found: $BUILD_SH" >&2; exit 3; } +[[ -x "$CMD" ]] || { echo "UnrealEditor-Cmd not found: $CMD" >&2; exit 3; } + +# Pre-flight: refuse to run while an editor instance has the project locked. +# Match only the actual UnrealEditor binary, not the project string in our own +# command line. +if pgrep -fa "/UnrealEditor( |$)" >/dev/null 2>&1; then + echo "ERROR: an UnrealEditor process is running. Close it first." >&2 + exit 3 +fi + +: > "$LOG" + +# --- Build ----------------------------------------------------------------- +echo ">>> Building $TARGET (Linux Development)..." +BUILD_OUT=$("$BUILD_SH" "$TARGET" Linux Development "-Project=$PROJECT" -WaitMutex 2>&1 || true) +echo "$BUILD_OUT" | tail -10 +if echo "$BUILD_OUT" | grep -q "Result: Succeeded\|Target is up to date"; then + BUILD_STATUS="Succeeded" +else + BUILD_STATUS="Failed" +fi + +# --- Stage runtime libs --------------------------------------------------- +# UBT's auto-RPATH for plugins symlinked outside the host project resolves +# incorrectly. Symlink third-party .so files into the plugin's Binaries/Linux/ +# so the loader resolves them via ${ORIGIN} (which UBT does add correctly). +SETUP_RUNTIME="$(dirname "$0")/setup_runtime_linux.sh" +if [[ "$BUILD_STATUS" == "Succeeded" && -x "$SETUP_RUNTIME" ]]; then + echo ">>> Staging third-party runtime libs..." + bash "$SETUP_RUNTIME" || echo "WARN: setup_runtime_linux.sh failed (continuing)" >&2 +fi + +# --- Test ------------------------------------------------------------------ +PASS=0 +FAIL=0 +TOTAL=0 +TESTS_PERFORMED_LINE="" + +if [[ "$BUILD_STATUS" == "Succeeded" ]]; then + echo ">>> Running automation tests (filter=$FILTER, log=$LOG)..." + "$CMD" "$PROJECT" \ + -ExecCmds="Automation RunTests $FILTER" \ + -Unattended -NullRHI -NoSound -NoSplash -stdout -log \ + -TestExit="Automation Test Queue Empty" \ + > "$LOG" 2>&1 || true + + if [[ ! -s "$LOG" ]]; then + echo "ERROR: test log is empty — editor likely held the project lock." >&2 + fi + + PASS=$(grep -cE 'Result=\{Success\}' "$LOG" || true) + FAIL=$(grep -cE 'Result=\{(Fail|Error)\}' "$LOG" || true) + TOTAL=$((PASS + FAIL)) + TESTS_PERFORMED_LINE=$(grep -oE '[0-9]+ tests performed' "$LOG" | tail -1 || true) +fi + +# --- Fingerprint + summary ------------------------------------------------- +# Note: Host and Log-path lines are intentionally omitted from the summary +# block because they leak the reporter's hostname / username on local runs. +# The SHA-256 of the log is sufficient for reviewers to verify content +# integrity when they re-run the suite themselves. +TS=$(date -u '+%Y-%m-%d %H:%M:%S UTC') +GIT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +GIT_SHA=$(git -C "$GIT_DIR" rev-parse --short=8 HEAD 2>/dev/null || echo "unknown") +GIT_BRANCH=$(git -C "$GIT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + +# Strip the engine path down to its last segment (e.g. 'UnrealEngine'). The +# full path can contain the user's install root on a non-standard layout. +ENGINE_LABEL=$(basename "$ENGINE") +[[ -z "$ENGINE_LABEL" ]] && ENGINE_LABEL="$ENGINE" + +# Third-party dep SHAs from third_party/install//INSTALLED_SHA.txt +# (written by the CMake build scripts). Lets a reviewer verify they're +# comparing against the same binary toolchain. Silent skip if missing. +DEPS_LINE="" +for pair in "mj:MuJoCo" "coacd:CoACD" "zmq:libzmq"; do + key="${pair%%:*}" + dir="${pair##*:}" + sha_file="$GIT_DIR/third_party/install/$dir/INSTALLED_SHA.txt" + if [[ -s "$sha_file" ]]; then + sha=$(head -c 7 "$sha_file") + if [[ -n "$DEPS_LINE" ]]; then DEPS_LINE="$DEPS_LINE "; fi + DEPS_LINE="${DEPS_LINE}${key}=${sha}" + fi +done +[[ -z "$DEPS_LINE" ]] && DEPS_LINE="unavailable" + +LOG_HASH="n/a" +if [[ -s "$LOG" ]]; then + if command -v sha256sum >/dev/null 2>&1; then + LOG_HASH=$(sha256sum "$LOG" | cut -c1-16) + fi +fi + +cat <&2 + echo "Run third_party/build_all.sh first." >&2 + exit 1 +fi + +if [[ ! -d "$BIN_DIR" ]]; then + # Fresh checkout: plugin .so hasn't been built yet. Skip silently with a + # one-line note so per-dep build.sh invocations during initial + # third-party setup don't error out. The next plugin build will create + # Binaries/Linux/, and the next call to this script (from build.sh, + # build_all.sh, or build_and_test_linux.sh) will populate the symlinks. + echo "Skipping runtime stage: $BIN_DIR doesn't exist yet (build the plugin first)." + exit 0 +fi + +count=0 +for pkg_lib in "$THIRD_PARTY/MuJoCo/lib" "$THIRD_PARTY/CoACD/lib" "$THIRD_PARTY/libzmq/lib"; do + [[ -d "$pkg_lib" ]] || continue + for so in "$pkg_lib"/*.so*; do + [[ -e "$so" ]] || continue + target="$BIN_DIR/$(basename "$so")" + ln -sfn "$so" "$target" + count=$((count + 1)) + done +done + +echo "Linked $count third-party shared libraries into $BIN_DIR" diff --git a/Source/URLab/Private/MuJoCo/Components/Tendons/MjTendon.cpp b/Source/URLab/Private/MuJoCo/Components/Tendons/MjTendon.cpp index fb753b1..e1094f7 100644 --- a/Source/URLab/Private/MuJoCo/Components/Tendons/MjTendon.cpp +++ b/Source/URLab/Private/MuJoCo/Components/Tendons/MjTendon.cpp @@ -245,7 +245,12 @@ void UMjTendon::RegisterToSpec(FMujocoSpecWrapper& Wrapper, mjsBody* ParentBody) case EMjTendonWrapType::Geom: { - const char* SideSiteStr = Wrap.SideSite.IsEmpty() ? "" : TCHAR_TO_UTF8(*Wrap.SideSite); + // FTCHARToUTF8 keeps the converted string alive for the + // duration of the wrapGeom call. Storing TCHAR_TO_UTF8 macro + // output in a const char* would dangle on Linux (see + // MjSpecWrapper::AddDefault for the same fix). + FTCHARToUTF8 SideSiteConv(*Wrap.SideSite); + const char* SideSiteStr = Wrap.SideSite.IsEmpty() ? "" : SideSiteConv.Get(); mjsWrap* W = mjs_wrapGeom(Tendon, TCHAR_TO_UTF8(*Wrap.TargetName), SideSiteStr); if (!W) { diff --git a/Source/URLab/Private/MuJoCo/Core/MjPhysicsEngine.cpp b/Source/URLab/Private/MuJoCo/Core/MjPhysicsEngine.cpp index a639ed6..8f32ffc 100644 --- a/Source/URLab/Private/MuJoCo/Core/MjPhysicsEngine.cpp +++ b/Source/URLab/Private/MuJoCo/Core/MjPhysicsEngine.cpp @@ -109,9 +109,11 @@ static void URLab_InstallMujocoCallbacks() { if (GMujocoCallbacksInstalled) return; - // mujoco.dll is delay-loaded in URLab's Build.cs, and the linker refuses - // to bind data symbols through a delayed import. Resolve the two - // mju_user_* function pointers manually via GetDllExport. +#if PLATFORM_WINDOWS + // mujoco.dll is delay-loaded on Windows (URLab.Build.cs adds it via + // PublicDelayLoadDLLs), and the linker refuses to bind data symbols + // through a delayed import. Resolve the two mju_user_* function + // pointers manually via GetDllExport. void* Handle = FPlatformProcess::GetDllHandle(TEXT("mujoco.dll")); if (!Handle) { @@ -127,6 +129,16 @@ static void URLab_InstallMujocoCallbacks() if (PErr) { *PErr = &URLab_OnMujocoError; } if (PWarn) { *PWarn = &URLab_OnMujocoWarning; } UE_LOG(LogURLab, Log, TEXT("[URLab] MuJoCo error callbacks installed (err=%p warn=%p)"), (void*)PErr, (void*)PWarn); +#else + // On Linux/macOS the lib is linked directly (no delay-load), so + // mju_user_error / mju_user_warning are resolvable as ordinary BSS + // data symbols at link time. Direct assignment is enough — no + // GetDllHandle / GetDllExport, no hardcoded SONAME literal needed. + mju_user_error = &URLab_OnMujocoError; + mju_user_warning = &URLab_OnMujocoWarning; + UE_LOG(LogURLab, Log, TEXT("[URLab] MuJoCo error callbacks installed (direct)")); +#endif + GMujocoCallbacksInstalled = true; } diff --git a/Source/URLab/Private/MuJoCo/Core/Spec/MjSpecWrapper.cpp b/Source/URLab/Private/MuJoCo/Core/Spec/MjSpecWrapper.cpp index 976bb52..f36d453 100644 --- a/Source/URLab/Private/MuJoCo/Core/Spec/MjSpecWrapper.cpp +++ b/Source/URLab/Private/MuJoCo/Core/Spec/MjSpecWrapper.cpp @@ -159,12 +159,20 @@ void FMujocoSpecWrapper::AddDefault(UMjDefault* DefaultComp) { if (!DefaultComp) return; - const char* ClassName = DefaultComp->ClassName.IsEmpty() ? nullptr : TCHAR_TO_UTF8(*DefaultComp->ClassName); - + // TCHAR_TO_UTF8 returns a pointer into a temporary that dies at the end of + // the full expression. Storing it in a long-lived variable produces a + // dangling pointer; on Linux/clang the stack is reclaimed before the value + // is used and the class registers under a garbage name, breaking + // childclass / inheritance resolution. Use FTCHARToUTF8 with explicit + // function-scope lifetime instead. + FTCHARToUTF8 ClassNameConv(*DefaultComp->ClassName); + const char* ClassName = DefaultComp->ClassName.IsEmpty() ? nullptr : ClassNameConv.Get(); + mjsDefault* parentDef = nullptr; if (!DefaultComp->ParentClassName.IsEmpty()) { - parentDef = mjs_findDefault(Spec, TCHAR_TO_UTF8(*DefaultComp->ParentClassName)); + FTCHARToUTF8 ParentNameConv(*DefaultComp->ParentClassName); + parentDef = mjs_findDefault(Spec, ParentNameConv.Get()); if (parentDef) { UE_LOG(LogURLabWrapper, Log, TEXT("[AddDefault] Linked Class '%s' to Parent '%s'"), *DefaultComp->ClassName, *DefaultComp->ParentClassName); diff --git a/Source/URLab/Private/URLab.cpp b/Source/URLab/Private/URLab.cpp index acdffa6..5d25b21 100644 --- a/Source/URLab/Private/URLab.cpp +++ b/Source/URLab/Private/URLab.cpp @@ -37,13 +37,16 @@ void FURLabModule::StartupModule() FString PluginDir = IPluginManager::Get().FindPlugin("UnrealRoboticsLab")->GetBaseDir(); FString InstallDir = FPaths::Combine(PluginDir, TEXT("third_party/install")); - // Function to load a DLL and log success/failure - auto LoadDependencyDLL = [&](const FString& LibraryName, const FString& SubDir) { + // Function to load a shared library and log success/failure. BinSubDir is + // the per-package subdirectory under third_party/install// that + // holds the loadable artifact: "bin" on Windows (DLLs), "lib" on Linux + // (.so files). + auto LoadDependencyDLL = [&](const FString& LibraryName, const FString& SubDir, const FString& BinSubDir) { // Try plugin third-party path first (editor / development) - FString DLLPath = FPaths::Combine(InstallDir, SubDir, TEXT("bin"), LibraryName); + FString DLLPath = FPaths::Combine(InstallDir, SubDir, BinSubDir, LibraryName); if (!FPaths::FileExists(DLLPath)) { - // Packaged build: DLLs staged next to the executable + // Packaged build: shared libs staged next to the executable DLLPath = FPaths::Combine(FPlatformProcess::GetModulesDirectory(), LibraryName); } if (FPaths::FileExists(DLLPath)) { @@ -60,17 +63,21 @@ void FURLabModule::StartupModule() return false; }; +#if PLATFORM_WINDOWS // Load MuJoCo. Since 3.7.0 the obj/stl decoders are compiled into // mujoco.dll itself (changelog item 9); loading the standalone // obj_decoder.dll / stl_decoder.dll would cause a plugin-registration // collision and crash during module init. - LoadDependencyDLL(TEXT("mujoco.dll"), TEXT("MuJoCo")); - - // Load ZMQ - LoadDependencyDLL(TEXT("libzmq-v143-mt-4_3_6.dll"), TEXT("libzmq")); - - // Load CoACD (Shared library) - LoadDependencyDLL(TEXT("lib_coacd.dll"), TEXT("CoACD")); + LoadDependencyDLL(TEXT("mujoco.dll"), TEXT("MuJoCo"), TEXT("bin")); + LoadDependencyDLL(TEXT("libzmq-v143-mt-4_3_6.dll"), TEXT("libzmq"), TEXT("bin")); + LoadDependencyDLL(TEXT("lib_coacd.dll"), TEXT("CoACD"), TEXT("bin")); +#elif PLATFORM_LINUX + // Linux .so layout: third_party/install//lib/. Names are SONAMEs + // produced by the upstream cmake builds. + LoadDependencyDLL(TEXT("libmujoco.so.3.7.0"), TEXT("MuJoCo"), TEXT("lib")); + LoadDependencyDLL(TEXT("libzmq.so.5"), TEXT("libzmq"), TEXT("lib")); + LoadDependencyDLL(TEXT("lib_coacd.so"), TEXT("CoACD"), TEXT("lib")); +#endif // Some CoACD dependencies like TBB or runtimes might be in CoACD/bin // They should be loaded automatically if in search path, but we can verify here if needed. diff --git a/Source/URLab/Public/CoACD/CoacdInterface.h b/Source/URLab/Public/CoACD/CoacdInterface.h index f70ff72..5d7ea7a 100644 --- a/Source/URLab/Public/CoACD/CoacdInterface.h +++ b/Source/URLab/Public/CoACD/CoacdInterface.h @@ -27,7 +27,7 @@ #include #include #include "Chaos/ArrayCollectionArray.h" -#include "Coacd/coacd.h" +#include "CoACD/coacd.h" #include "Misc/FileHelper.h" namespace CoacdInterface { diff --git a/Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h b/Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h index 523ffb4..0d6ce37 100644 --- a/Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h +++ b/Source/URLab/Public/MuJoCo/Components/Sensors/MjCamera.h @@ -137,7 +137,7 @@ class URLAB_API UMjCamera : public UMjComponent UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MuJoCo|Camera|Network") bool bEnableZmqBroadcast = false; - /** @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. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MuJoCo|Camera|Network") FString ZmqEndpoint = TEXT("tcp://*:5558"); diff --git a/Source/URLab/Public/URLab.h b/Source/URLab/Public/URLab.h index 3e8c857..c359503 100644 --- a/Source/URLab/Public/URLab.h +++ b/Source/URLab/Public/URLab.h @@ -24,8 +24,8 @@ #include "CoreMinimal.h" #include "Modules/ModuleManager.h" -#ifndef _WIN32 - #define _WIN32 PLATFORM_WINDOWS +#if PLATFORM_WINDOWS && !defined(_WIN32) + #define _WIN32 1 #endif class FURLabModule : public IModuleInterface { diff --git a/Source/URLab/URLab.Build.cs b/Source/URLab/URLab.Build.cs index a2daeeb..e821cd0 100644 --- a/Source/URLab/URLab.Build.cs +++ b/Source/URLab/URLab.Build.cs @@ -83,16 +83,9 @@ public URLab(ReadOnlyTargetRules Target) : base(Target) if (Target.Platform == UnrealTargetPlatform.Linux) { - // Ensure Unreal recognizes MuJoCo's export macros - } - - - if (Target.Platform == UnrealTargetPlatform.Linux) - { - PublicDefinitions.Add("_WIN32=0"); - PublicDefinitions.Add("USE_DECLSPEC=1"); - PublicDefinitions.Add("__linux__=1"); - PublicDefinitions.Add("__unix__=1"); + // Don't define _WIN32 on Linux: `#if defined _WIN32` is true even + // when _WIN32 is 0, which sends MuJoCo's mjexport.h down the + // __declspec(dllimport) branch and breaks clang on Linux. } @@ -150,7 +143,19 @@ private void AddThirdPartyLibrary(string LibraryName, ReadOnlyTargetRules Target } else if (Target.Platform == UnrealTargetPlatform.Linux) { - // Link all static libraries and shared objects for Linux + // On Linux the URLab plugin .so already gets `${ORIGIN}` added to + // its RPATH by UBT, so the runtime loader resolves third-party + // shared libs (libmujoco / lib_coacd / libzmq) from the plugin's + // own Binaries/Linux/ directory. The `setup_rpath_linux.sh` + // helper symlinks third_party/install//lib/*.so* into + // Binaries/Linux/ after the plugin builds, so no LD_LIBRARY_PATH + // is needed at editor / packaging time. + // + // (UBT's auto-computed relative RPATH from absolute lib paths + // resolves incorrectly when the plugin lives outside the host + // project — the ${ORIGIN}/../../../../UnrealEngine/.. chain it + // emits assumes engine and project share a common ancestor. + // Staging via $ORIGIN sidesteps that entirely.) string LibPath = Path.Combine(FullPath, "lib"); if (Directory.Exists(LibPath)) { @@ -158,19 +163,25 @@ private void AddThirdPartyLibrary(string LibraryName, ReadOnlyTargetRules Target { PublicAdditionalLibraries.Add(LibFile); } + // Link against the unversioned .so (symlink) so the linker + // records the SONAME, not an absolute versioned path. foreach (string LibFile in Directory.GetFiles(LibPath, "*.so", SearchOption.AllDirectories)) { PublicAdditionalLibraries.Add(LibFile); - RuntimeDependencies.Add(LibFile); - PublicDelayLoadDLLs.Add(Path.GetFileName(LibFile)); + } + // Stage every *.so* (including version-suffixed real files) + // next to the plugin .so so the SONAME chain resolves at + // runtime under ${ORIGIN}. + foreach (string LibFile in Directory.GetFiles(LibPath, "*.so*", SearchOption.AllDirectories)) + { + RuntimeDependencies.Add("$(BinaryOutputDir)/" + Path.GetFileName(LibFile), LibFile, StagedFileType.NonUFS); } } - // Add binaries (often plugin/shared objects) for Linux string BinPath = Path.Combine(FullPath, "bin"); if (Directory.Exists(BinPath)) { - foreach (string BinFile in Directory.GetFiles(BinPath, "*.so", SearchOption.AllDirectories)) + foreach (string BinFile in Directory.GetFiles(BinPath, "*.so*", SearchOption.AllDirectories)) { RuntimeDependencies.Add(BinFile); PublicDelayLoadDLLs.Add(Path.GetFileName(BinFile)); diff --git a/docs/linux_setup.md b/docs/linux_setup.md new file mode 100644 index 0000000..816fd11 --- /dev/null +++ b/docs/linux_setup.md @@ -0,0 +1,179 @@ +# Linux Setup + +URLab supports Linux against Unreal Engine 5.7+. The plugin builds and the full automation suite (`URLab.*`) passes 177/177 with a working editor — but the build flow is more involved than on Windows because UE's bundled clang + libc++ require the third-party dependencies to be ABI-compatible. + +This page is a **one-time setup** walkthrough. Jump to [Day-to-day workflow](#day-to-day-workflow) once you have a working build; the [Troubleshooting / Advanced](#troubleshooting--advanced) section covers debugging tips and the build flags applied internally by `build_all.sh --engine`. + +If you hit anything not covered here please open an issue. + +## Prerequisites + +### Unreal Engine 5 + +You have two options. The binary is dramatically faster to set up; the source build is useful when you want to debug into the engine itself. + +#### Option A — precompiled binary (recommended for most users) + +Epic ships a precompiled UE5 binary for Linux — no source build, no GitHub access, no `Setup.sh`. + +1. Sign in to your Epic Games account at (top of the page). +2. Download **Linux Unreal Engine** — currently a single `.zip` named like `Linux_Unreal_Engine_5.7.x.zip`, ~25 GB compressed / ~43 GB extracted. +3. Extract anywhere. The rest of this guide refers to the extract root as `$UE_ROOT` (e.g. `/home//UnrealEngine`). +4. Confirm the editor binary exists and is executable: + + ```bash + ls -la $UE_ROOT/Engine/Binaries/Linux/UnrealEditor + ``` + +#### Option B — source build (useful for engine-level debugging) + +Lets you set breakpoints in engine code, step through UBT, and patch UE itself. Costs a multi-hour clone + compile and ~150 GB disk; rarely needed for plugin work but invaluable when you do need it. + +1. [Link your Epic Games account to GitHub](https://www.unrealengine.com/en-US/ue-on-github) so you can access . +2. Clone the `5.7` branch (or `release` for whatever Epic considers stable): + + ```bash + git clone -b 5.7 https://github.com/EpicGames/UnrealEngine.git + cd UnrealEngine + ``` + +3. Run the bundled setup + project-files scripts: + + ```bash + ./Setup.sh # downloads ~30 GB of binary deps via Git LFS + ./GenerateProjectFiles.sh # generates Makefile / IDE project files + make UnrealEditor # ~hours on first build, parallel-safe + ``` + +4. `$UE_ROOT` is your `UnrealEngine/` clone root for the rest of this doc. + +#### General notes (apply to both options) + +- **Disk-space rule of thumb:** 70 GB free for option A initial setup is comfortable (~43 GB UE binaries + ~10–20 GB shader / DDC cache + a few GB for project + plugin). Option B needs ~150 GB. +- **Display:** the editor needs a Wayland or X11 session. On a headless server, common options are NICE DCV, X2Go, or a TigerVNC `:1` display (`export DISPLAY=:1` before launching). +- **System libs:** UE bundles most of what it needs (libc++, ICU, etc.). On a fresh Ubuntu 22.04 you may still need `apt install libsdl2-2.0-0 libvulkan1` if the editor can't open a window — that's the symptom you'd see after launch with a missing-library message in the log. + +### Other prerequisites + +- **CMake 3.24+** — Ubuntu 22.04 ships 3.22, which is below CoACD's minimum. Easiest fix: + + ```bash + pip install --user "cmake>=3.24,<4" + export PATH="$HOME/.local/bin:$PATH" + ``` + +- **A host UE5 C++ project** with `UnrealRoboticsLab` cloned into its `Plugins/` (or symlinked there). The host project must be C++; if your project is Blueprints-only, add a dummy C++ class in the editor first (Tools → New C++ Class). + +The plugin's third-party submodules (`MuJoCo`, `CoACD`, `libzmq`) must be initialised: + +```bash +cd UnrealRoboticsLab +git submodule update --init --recursive +``` + +## Why a special build flow + +UE on Linux uses its own bundled clang (currently 20.1.x at `$UE_ROOT/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/...`) and links against its bundled libc++. If you build the third-party libs with the system gcc + libstdc++ (which is what `third_party/build_all.sh` does *without* `--engine`), the resulting `.so` files have a different C++ ABI than the UE plugin link expects, and you'll get a wall of `std::*` undefined-symbol errors at link time. + +`build_all.sh --engine $UE_ROOT` points the third-party CMake builds at UE's clang + libc++. Once the libs are built that way, the plugin links and runs against them cleanly. + +## One-time setup + +```bash +UE_ROOT=/path/to/UnrealEngine +URLAB_ROOT=$UE_ROOT/../URLabTest/Plugins/UnrealRoboticsLab # adjust to your layout +``` + +### 1. Build third-party libs against UE's toolchain + +```bash +cd "$URLAB_ROOT/third_party" +./build_all.sh --engine "$UE_ROOT" +``` + +Expected on success: `third_party/install/{MuJoCo,CoACD,libzmq}/` populated with headers + `.so` files. + +`--engine` makes the script glob `Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v*_clang-*/` (version-sorted, so future UE bumps from v26 → v27 are picked up automatically), then export `CC / CXX / AR / RANLIB / CFLAGS / CXXFLAGS / LDFLAGS` for each per-dep `build.sh`. + +### 2. Generate UE project files and build the editor target + +```bash +cd "$UE_ROOT" +Engine/Build/BatchFiles/Linux/GenerateProjectFiles.sh -project=/path/to/HostProject.uproject -game -engine +Engine/Build/BatchFiles/Linux/Build.sh HostProjectEditor Linux Development -Project=/path/to/HostProject.uproject +``` + +The third-party `.so` files (libmujoco, lib_coacd, libzmq) get symlinked into the plugin's `Binaries/Linux/` automatically by each per-dep `build.sh` and by `Scripts/build_and_test_linux.sh` — see [How runtime staging works](#how-runtime-staging-works) in Troubleshooting / Advanced if you need to do it manually. + +### 3. Launch the editor + +```bash +DISPLAY=:1 "$UE_ROOT/Engine/Binaries/Linux/UnrealEditor" /path/to/HostProject.uproject +``` + +`URLab` should appear under loaded plugins, the **MuJoCo** asset category should register, and the MJCF importer should respond when you drag an `.xml` into the Content Browser. + +## Day-to-day workflow + +After the one-time setup, normal iteration is just: + +```bash +git pull +./Scripts/build_and_test_linux.sh \ + --engine "$UE_ROOT" \ + --project /path/to/HostProject.uproject +``` + +That builds the editor target (incremental), re-stages the runtime symlinks if a third-party dep was rebuilt, runs the `URLab.*` automation suite, and emits the build+test summary block to paste into a PR. Expected: `177 / 177 passed`. + +Close the editor before running — the test harness needs the project lock free. + +If you only changed plugin C++ and want to skip the test pass, the editor `Build.sh` step from one-time setup #2 is the inner loop. + +## Troubleshooting / Advanced + +### How runtime staging works + +UE on Linux doesn't auto-stage `RuntimeDependencies` for editor builds, and UBT's auto-computed RPATH for plugins symlinked outside the host project can resolve incorrectly. URLab works around this by symlinking the third-party `.so` files into the plugin's `Binaries/Linux/` so the loader resolves them via `${ORIGIN}` (which UBT does add correctly). + +The helper `Scripts/setup_runtime_linux.sh` does the symlinking. It's idempotent and warn-skips when `Binaries/Linux/` doesn't exist yet (first-time fresh checkout, before the plugin .so has been built). You **don't normally call it directly** — both `build_all.sh` (via each per-dep `build.sh`) and `Scripts/build_and_test_linux.sh` invoke it after their respective build steps. + +You'd run it manually only if you fiddled with `third_party/install//lib/` outside of those scripts and need to re-sync the symlinks: + +```bash +"$URLAB_ROOT/Scripts/setup_runtime_linux.sh" +``` + +For packaged (non-editor) builds, `RuntimeDependencies.Add("$(BinaryOutputDir)/...", LibFile, NonUFS)` from `URLab.Build.cs` stages the libs through `BuildCookRun`, and UBT's `${ORIGIN}` RPATH resolves them — no manual step needed. + +### Build flags applied internally by `build_all.sh --engine` + +Reference, in case you need to debug a per-dep failure or replicate the build manually: + +- `-Wno-unknown-warning-option` lets clang ignore `-Werror=stringop-overflow` (a GCC-only flag) that TBB (CoACD's transitive dep) tries to use. +- `-Wno-missing-template-arg-list-after-template-kw` keeps clang 20 from rejecting OpenVDB's `OpT::template eval(...)` syntax. +- `-Qunused-arguments` quiets MuJoCo's `-Werror -Wunused-command-line-argument` noise from `-stdlib=libc++` on compile-only steps. +- `BUILD_STATIC=OFF` is applied automatically for libzmq on Linux (in `third_party/libzmq/build.sh`) — the static archive's `mailbox_safe.cpp` pulls `pthread_cond_clockwait` which UE's link sysroot can't resolve, and the `.so` works fine. + +### Building one dep manually (env-var sandwich) + +If you want to rebuild only one dep with the same flags `build_all.sh --engine` would apply (e.g. iterating on MuJoCo locally), the equivalent invocation is: + +```bash +UE_TC=$(ls -d "$UE_ROOT/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64"/v*_clang-*/x86_64-unknown-linux-gnu | sort -V | tail -1) + +CC="$UE_TC/bin/clang" \ +CXX="$UE_TC/bin/clang++" \ +AR="$UE_TC/bin/llvm-ar" \ +RANLIB="$UE_TC/bin/llvm-ranlib" \ +CFLAGS="-fPIC -Qunused-arguments -Wno-unknown-warning-option" \ +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" \ +LDFLAGS="-stdlib=libc++ -fuse-ld=lld -L$UE_TC/lib64 -Wl,-rpath,$UE_TC/lib64" \ +bash third_party/MuJoCo/build.sh +``` + +### Known caveats + +- **Plugin must be located inside (or symlinked inside) the host project's `Plugins/` directory.** UBT's auto-RPATH calculation makes assumptions about the relative position of the plugin and the engine; we work around them by staging libs into `${ORIGIN}`, but the plugin still needs to be findable to UBT through the host project's tree. +- **PIE editor first launch compiles Vulkan SM5 shaders + a derived-data cache** (~10–20 GB). Plan for it; subsequent launches are fast. +- **The `urlab_bridge` Python package** (separate repo) hasn't been smoke-tested against this Linux flow yet. Open an issue if you hit problems. diff --git a/mkdocs.yml b/mkdocs.yml index a944f57..cc77da4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ nav: - Home: index.md - Guides: - Getting Started: getting_started.md + - Linux Setup: linux_setup.md - Features: features.md - Architecture: architecture.md - MJCF Import: guides/mujoco_import.md diff --git a/third_party/CoACD/build.ps1 b/third_party/CoACD/build.ps1 index 810e772..fc56abd 100644 --- a/third_party/CoACD/build.ps1 +++ b/third_party/CoACD/build.ps1 @@ -53,6 +53,11 @@ if (Test-Path "../../CoACD_custom/cmake") { if (-not (Test-Path "cmake")) { New-Item -ItemType Directory -Path "cmake" | Out-Null } Copy-Item -Path "../../CoACD_custom/cmake/*" -Destination "cmake/" -Recurse -Force } +# Overlay header patches (see build.sh comment for rationale). +if (Test-Path "../../CoACD_custom/public") { + if (-not (Test-Path "public")) { New-Item -ItemType Directory -Path "public" | Out-Null } + Copy-Item -Path "../../CoACD_custom/public/*" -Destination "public/" -Recurse -Force +} if (-not (Test-Path "build")) { New-Item -ItemType Directory -Path "build" } cd build diff --git a/third_party/CoACD/build.sh b/third_party/CoACD/build.sh index 6d4d5b7..afce0c7 100644 --- a/third_party/CoACD/build.sh +++ b/third_party/CoACD/build.sh @@ -53,6 +53,13 @@ if [ -d "../../CoACD_custom/cmake" ]; then mkdir -p cmake cp -rf ../../CoACD_custom/cmake/* cmake/ fi +# Overlay header patches (e.g. public/coacd.h relaxes `#if _WIN32` to +# `#if defined(_WIN32)` so URLab's build does not trip clang's -Wundef under +# UE on Linux). +if [ -d "../../CoACD_custom/public" ]; then + mkdir -p public + cp -rf ../../CoACD_custom/public/* public/ +fi mkdir -p build cd build @@ -71,3 +78,11 @@ cd ../.. # Record the exact source SHA we just installed from (see MuJoCo/build.ps1). echo "$INSTALLED_SHA" > "$INSTALL_DIR/INSTALLED_SHA.txt" echo "Recorded INSTALLED_SHA=$INSTALLED_SHA at $INSTALL_DIR/INSTALLED_SHA.txt" + +# Linux: re-stage Binaries/Linux/ symlinks. See MuJoCo/build.sh for rationale. +if [ "$(uname -s)" = "Linux" ]; then + SETUP_RUNTIME="$(cd "$(dirname "$0")/../../Scripts" 2>/dev/null && pwd)/setup_runtime_linux.sh" + if [ -x "$SETUP_RUNTIME" ]; then + bash "$SETUP_RUNTIME" || echo "WARN: setup_runtime_linux.sh failed (continuing)" >&2 + fi +fi diff --git a/third_party/CoACD_custom/public/coacd.h b/third_party/CoACD_custom/public/coacd.h new file mode 100644 index 0000000..21385bc --- /dev/null +++ b/third_party/CoACD_custom/public/coacd.h @@ -0,0 +1,75 @@ +// URLab CoACD overlay header. Build scripts copy this onto +// third_party/CoACD/src/public/coacd.h before configure so the upstream +// `#if _WIN32` test (which evaluates an undefined macro to 0 and trips +// `-Werror=undef` under UE's clang on Linux) becomes a `defined()` check. +// +// Diff vs upstream: line 10, `#if _WIN32` -> `#if defined(_WIN32)`. + +#pragma once +#include +#include +#include +#include +#include + +namespace coacd { + +#if defined(_WIN32) +#define COACD_API __declspec(dllexport) +#else +#define COACD_API +#endif + +struct Mesh { + std::vector> vertices; + std::vector> indices; +}; + +std::vector CoACD(Mesh const &input, double threshold = 0.05, + int max_convex_hull = -1, std::string preprocess = "auto", + int prep_resolution = 50, int sample_resolution = 2000, + int mcts_nodes = 20, int mcts_iteration = 150, + int mcts_max_depth = 3, bool pca = false, + bool merge = true, bool decimate = false, int max_ch_vertex = 256, + bool extrude = false, double extrude_margin = 0.01, + std::string apx_mode = "ch", unsigned int seed = 0, + bool real_metric = false); +void set_log_level(std::string_view level); + +} // namespace coacd + +extern "C" { + +struct CoACD_Mesh { + double *vertices_ptr; + uint64_t vertices_count; + int *triangles_ptr; + uint64_t triangles_count; +}; + +struct CoACD_MeshArray { + CoACD_Mesh *meshes_ptr; + uint64_t meshes_count; +}; + +void COACD_API CoACD_freeMeshArray(CoACD_MeshArray arr); + +constexpr int preprocess_auto = 0; +constexpr int preprocess_on = 1; +constexpr int preprocess_off = 2; + +constexpr int apx_ch = 0; +constexpr int apx_box = 1; + +CoACD_MeshArray COACD_API CoACD_run(CoACD_Mesh const &input, double threshold, + int max_convex_hull, int preprocess_mode, + int prep_resolution, int sample_resolution, + int mcts_nodes, int mcts_iteration, + int mcts_max_depth, bool pca, bool merge, + bool decimate, int max_ch_vertex, + bool extrude, double extrude_margin, + int apx_mode, unsigned int seed, + bool real_metric); + +void COACD_API CoACD_setLogLevel(char const *level); +} diff --git a/third_party/MuJoCo/build.sh b/third_party/MuJoCo/build.sh index 473c0e8..0192d25 100644 --- a/third_party/MuJoCo/build.sh +++ b/third_party/MuJoCo/build.sh @@ -71,3 +71,15 @@ cd ../.. # Record the exact source SHA we just installed from (see MuJoCo/build.ps1). echo "$INSTALLED_SHA" > "$INSTALL_DIR/INSTALLED_SHA.txt" echo "Recorded INSTALLED_SHA=$INSTALLED_SHA at $INSTALL_DIR/INSTALLED_SHA.txt" + +# Linux: re-stage the plugin's Binaries/Linux/ symlinks so a per-dep rebuild +# (e.g. someone bumps MuJoCo's submodule SHA and runs only this script) +# doesn't leave stale symlinks pointing into a wiped install/MuJoCo/lib/. +# The helper exits cleanly with a one-line note if Binaries/Linux/ doesn't +# exist yet (first-time fresh checkout — plugin .so not built). +if [ "$(uname -s)" = "Linux" ]; then + SETUP_RUNTIME="$(cd "$(dirname "$0")/../../Scripts" 2>/dev/null && pwd)/setup_runtime_linux.sh" + if [ -x "$SETUP_RUNTIME" ]; then + bash "$SETUP_RUNTIME" || echo "WARN: setup_runtime_linux.sh failed (continuing)" >&2 + fi +fi diff --git a/third_party/build_all.sh b/third_party/build_all.sh index 0f793d3..ec4b4d7 100644 --- a/third_party/build_all.sh +++ b/third_party/build_all.sh @@ -1,24 +1,109 @@ #!/bin/bash -# Master Build Script for URLab Third-Party Dependencies (Linux) +# Master Build Script for URLab Third-Party Dependencies (Linux/macOS) # # Each dep's build.sh will sync its submodule (third_party//src) to the # SHA URLab expects before building. Pass --no-submodule-sync to skip the # sync across all three deps, e.g. when iterating on a submodule locally. +# +# Linux note: pass --engine to build against UE's bundled clang + +# libc++. Without it the system gcc + libstdc++ are used, which produces +# .so files whose C++ ABI doesn't match UE's plugin link (you'll get a wall +# of std::* undefined-symbol errors during the URLab plugin link step). +# Worse, with some toolchain combinations the system gcc emits LTO bitcode +# objects rather than real .so files, which fail to load with a cryptic +# "file too short" at editor startup. The --engine path makes the toolchain +# explicit. + +set -e -ROOT_DIR=$(pwd) +ROOT_DIR=$(cd "$(dirname "$0")" && pwd) INSTALL_DIR="$ROOT_DIR/install" BUILD_TYPE="Release" +ENGINE="" + +usage() { + cat >&2 <<'HELP' +build_all.sh — build URLab's third-party dependencies (MuJoCo, CoACD, libzmq). +Usage: + ./third_party/build_all.sh [--engine ] [--no-submodule-sync] + +Options: + --engine Linux only: point CC/CXX/AR/RANLIB at UE's bundled + clang + libc++ under /Engine/Extras/..., + and inject the matching CFLAGS/CXXFLAGS/LDFLAGS so + the resulting .so files are ABI-compatible with + UE's plugin link. Strongly recommended on Linux. + --no-submodule-sync Skip `git submodule update` on each dep's src/. + Use when iterating on a submodule locally. +HELP + exit 3 +} + +# Pre-parse args. We collect --no-submodule-sync into SHARED_ARGS so it +# propagates to each per-dep build.sh; --engine is consumed here and used +# only to set CC/CXX/CFLAGS/etc. SHARED_ARGS=() -for arg in "$@"; do - if [ "$arg" = "--no-submodule-sync" ]; then SHARED_ARGS+=("--no-submodule-sync"); fi +while [[ $# -gt 0 ]]; do + case "$1" in + --engine) ENGINE="$2"; shift 2 ;; + --no-submodule-sync) SHARED_ARGS+=("--no-submodule-sync"); shift ;; + -h|--help) usage ;; + *) echo "Unknown arg: $1" >&2; usage ;; + esac done -# Ensure directories exist +# Linux: when --engine is given, locate UE's bundled clang under +# Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64// +# x86_64-unknown-linux-gnu/. Glob the v*_clang-* directory so future UE +# bumps (v26 -> v27) survive without a script edit. +if [[ -n "$ENGINE" && "$(uname -s)" = "Linux" ]]; then + SDK_BASE="$ENGINE/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64" + if [[ ! -d "$SDK_BASE" ]]; then + echo "ERROR: --engine path doesn't contain UE's Linux SDK base:" >&2 + echo " expected: $SDK_BASE" >&2 + exit 3 + fi + # Pick the highest-numbered v*_clang-* toolchain. Bash globs sort + # lexicographically so v25 sorts before v26 — version-aware sort with + # `sort -V` ensures v27 wins over v26 on a future UE bump. Latent today + # since UE ships one toolchain per engine version, but cheap insurance. + UE_TC=$(ls -d "$SDK_BASE"/v*_clang-*/x86_64-unknown-linux-gnu 2>/dev/null | sort -V | tail -1) + if [[ -z "$UE_TC" || ! -x "$UE_TC/bin/clang++" ]]; then + echo "ERROR: no v*_clang-*/x86_64-unknown-linux-gnu toolchain found under $SDK_BASE" >&2 + exit 3 + fi + echo "Using UE toolchain: $UE_TC" + + export CC="$UE_TC/bin/clang" + export CXX="$UE_TC/bin/clang++" + export AR="$UE_TC/bin/llvm-ar" + export RANLIB="$UE_TC/bin/llvm-ranlib" + + # Notes on the CXX/LDFLAGS choices: + # -nostdinc++ -isystem : force libc++ headers from UE's SDK + # so we don't accidentally pick up libstdc++ headers from the host. + # -stdlib=libc++: link against UE's libc++ at link time. + # -fuse-ld=lld: lld is what UE uses; avoids surprises on plugin link. + # -Qunused-arguments: silence the "argument unused during compilation" + # warning -stdlib=libc++ produces on .c-mode compile-only steps + # (some MuJoCo deps build with -Werror). + # -Wno-unknown-warning-option: lets clang ignore -Werror=stringop-overflow + # and similar GCC-only flags TBB (CoACD's transitive dep) uses. + # -Wno-missing-template-arg-list-after-template-kw: clang 20 (UE 5.7's + # bundled) rejects OpenVDB's `OpT::template eval(...)` syntax that + # older clang accepted. CoACD pulls OpenVDB transitively. + export CFLAGS="-fPIC -Qunused-arguments -Wno-unknown-warning-option" + export 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" + export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -L$UE_TC/lib64 -Wl,-rpath,$UE_TC/lib64" +fi + mkdir -p "$INSTALL_DIR" echo -e "\e[36mStarting Unified Build Process...\e[0m" +cd "$ROOT_DIR" + # 1. CoACD echo -e "\n\e[33m--- Building CoACD ---\e[0m" cd CoACD diff --git a/third_party/libzmq/build.sh b/third_party/libzmq/build.sh index 03fb0a1..6d31a78 100644 --- a/third_party/libzmq/build.sh +++ b/third_party/libzmq/build.sh @@ -48,10 +48,19 @@ mkdir -p build cd build echo "Configuring libzmq..." +# BUILD_STATIC=OFF on Linux: the libzmq static archive's mailbox_safe.cpp +# pulls libc++ wait_until -> pthread_cond_clockwait, which UE's link +# sysroot doesn't resolve. The shared .so links its own deps internally +# and works fine, so just don't build the static lib on Linux. +LIBZMQ_STATIC_FLAG="" +case "$(uname -s)" in + Linux) LIBZMQ_STATIC_FLAG="-DBUILD_STATIC=OFF" ;; +esac cmake .. -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DZMQ_BUILD_TESTS=OFF \ -DWITH_PERF_TOOL=OFF \ - -DENABLE_DRAFTS=OFF + -DENABLE_DRAFTS=OFF \ + $LIBZMQ_STATIC_FLAG echo "Building libzmq..." cmake --build . --config "$BUILD_TYPE" @@ -64,3 +73,11 @@ cd ../.. # Record the exact source SHA we just installed from (see MuJoCo/build.ps1). echo "$INSTALLED_SHA" > "$INSTALL_DIR/INSTALLED_SHA.txt" echo "Recorded INSTALLED_SHA=$INSTALLED_SHA at $INSTALL_DIR/INSTALLED_SHA.txt" + +# Linux: re-stage Binaries/Linux/ symlinks. See MuJoCo/build.sh for rationale. +if [ "$(uname -s)" = "Linux" ]; then + SETUP_RUNTIME="$(cd "$(dirname "$0")/../../Scripts" 2>/dev/null && pwd)/setup_runtime_linux.sh" + if [ -x "$SETUP_RUNTIME" ]; then + bash "$SETUP_RUNTIME" || echo "WARN: setup_runtime_linux.sh failed (continuing)" >&2 + fi +fi